diff --git a/chatterino.pro b/chatterino.pro index 07eed359..5e4794b4 100644 --- a/chatterino.pro +++ b/chatterino.pro @@ -79,7 +79,6 @@ SOURCES += \ src/singletons/emotemanager.cpp \ src/messages/messagebuilder.cpp \ src/twitch/twitchmessagebuilder.cpp \ - src/twitch/twitchparsemessage.cpp \ src/widgets/titlebar.cpp \ src/appdatapath.cpp \ src/singletons/accountmanager.cpp \ @@ -153,7 +152,6 @@ HEADERS += \ src/messages/messageparseargs.hpp \ src/messages/messagebuilder.hpp \ src/twitch/twitchmessagebuilder.hpp \ - src/twitch/twitchparsemessage.hpp \ src/widgets/titlebar.hpp \ src/appdatapath.hpp \ src/singletons/accountmanager.hpp \ diff --git a/src/channel.cpp b/src/channel.cpp index 15858d99..ee1ae9cf 100644 --- a/src/channel.cpp +++ b/src/channel.cpp @@ -1,9 +1,9 @@ #include "channel.hpp" #include "debug/log.hpp" -#include "singletons/emotemanager.hpp" -#include "singletons/ircmanager.hpp" #include "logging/loggingmanager.hpp" #include "messages/message.hpp" +#include "singletons/emotemanager.hpp" +#include "singletons/ircmanager.hpp" #include "singletons/windowmanager.hpp" #include @@ -36,9 +36,6 @@ messages::LimitedQueueSnapshot Channel::getMessageSnaps void Channel::addMessage(std::shared_ptr message) { - if (dontAddMessages) { - return; - } std::shared_ptr deleted; const QString &username = message->loginName; @@ -52,13 +49,22 @@ void Channel::addMessage(std::shared_ptr message) // _loggingChannel->append(message); // } - if (this->messages.appendItem(message, deleted)) { + if (this->messages.pushBack(message, deleted)) { messageRemovedFromStart(deleted); } this->messageAppended(message); } +void Channel::addMessagesAtStart(std::vector &messages) +{ + std::vector addedMessages = this->messages.pushFront(messages); + + if (addedMessages.size() != 0) { + this->messagesAddedAtStart(addedMessages); + } +} + void Channel::addRecentChatter(const std::shared_ptr &message) { assert(!message->loginName.isEmpty()); diff --git a/src/channel.hpp b/src/channel.hpp index 49625575..d726efac 100644 --- a/src/channel.hpp +++ b/src/channel.hpp @@ -26,11 +26,13 @@ public: boost::signals2::signal messageRemovedFromStart; boost::signals2::signal messageAppended; + boost::signals2::signal &)> messagesAddedAtStart; virtual bool isEmpty() const; messages::LimitedQueueSnapshot getMessageSnapshot(); void addMessage(messages::SharedMessage message); + void addMessagesAtStart(std::vector &messages); void addRecentChatter(const std::shared_ptr &message); struct NameOptions { @@ -50,8 +52,6 @@ public: virtual bool canSendMessage() const; virtual void sendMessage(const QString &message); - bool dontAddMessages = false; - private: messages::LimitedQueue messages; diff --git a/src/messages/limitedqueue.hpp b/src/messages/limitedqueue.hpp index f5d69fd5..8d19f716 100644 --- a/src/messages/limitedqueue.hpp +++ b/src/messages/limitedqueue.hpp @@ -1,5 +1,7 @@ #pragma once +#include "QDebug" + #include "messages/limitedqueuesnapshot.hpp" #include @@ -9,88 +11,193 @@ namespace chatterino { namespace messages { +// +// Warning: +// - this class is so overengineered it's not even funny anymore +// +// Explanation: +// - messages can be appended until 'limit' is reached +// - when the limit is reached for every message added one will be removed at the start +// - messages can only be added to the start when there is space for them, +// trying to add messages to the start when it's full will not add them +// - you are able to get a "Snapshot" which captures the state of this object +// - adding items to this class does not change the "items" of the snapshot +// + template class LimitedQueue { +protected: + typedef std::shared_ptr> Chunk; + typedef std::shared_ptr> ChunkVector; + public: - LimitedQueue(int _limit = 1000, int _buffer = 250) - : offset(0) - , limit(_limit) - , buffer(_buffer) + LimitedQueue(int _limit = 1000) + : limit(_limit) { - this->vector = std::make_shared>(); - this->vector->reserve(this->limit + this->buffer); + this->clear(); } void clear() { std::lock_guard lock(this->mutex); - this->vector = std::make_shared>(); - this->vector->reserve(this->limit + this->buffer); - - this->offset = 0; + this->chunks = std::make_shared>>>(); + Chunk chunk = std::make_shared>(); + chunk->resize(this->chunkSize); + this->chunks->push_back(chunk); + this->firstChunkOffset = 0; + this->lastChunkEnd = 0; } // return true if an item was deleted // deleted will be set if the item was deleted - bool appendItem(const T &item, T &deleted) + bool pushBack(const T &item, T &deleted) { std::lock_guard lock(this->mutex); - if (this->vector->size() >= this->limit) { - // vector is full - if (this->offset == this->buffer) { - deleted = this->vector->at(this->offset); + Chunk lastChunk = this->chunks->back(); - // create new vector - auto newVector = std::make_shared>(); - newVector->reserve(this->limit + this->buffer); + // still space in the last chunk + if (lastChunk->size() <= this->lastChunkEnd) { + // create new chunk vector + ChunkVector newVector = + std::make_shared>>>(); - for (unsigned int i = 0; i < this->limit; ++i) { - newVector->push_back(this->vector->at(i + this->offset)); - } - newVector->push_back(item); - - this->offset = 0; - this->vector = newVector; - - return true; - } else { - deleted = this->vector->at(this->offset); - - // append item and increment offset("deleting" first element) - this->vector->push_back(item); - this->offset++; - - return true; + // copy chunks + for (Chunk &chunk : *this->chunks) { + newVector->push_back(chunk); } - } else { - // append item - this->vector->push_back(item); - return false; + // push back new chunk + Chunk newChunk = std::make_shared>(); + newChunk->resize(this->chunkSize); + newVector->push_back(newChunk); + + // replace current chunk vector + this->chunks = newVector; + this->lastChunkEnd = 0; + lastChunk = this->chunks->back(); } + + lastChunk->at(this->lastChunkEnd++) = item; + + return this->deleteFirstItem(deleted); } + // returns a vector with all the accepted items + std::vector pushFront(const std::vector &items) + { + std::vector acceptedItems; + + if (this->space() > 0) { + std::lock_guard lock(this->mutex); + + // create new vector to clone chunks into + ChunkVector newChunks = + std::make_shared>>>(); + + newChunks->resize(this->chunks->size()); + + // copy chunks except for first one + for (size_t i = 1; i < this->chunks->size(); i++) { + newChunks->at(i) = this->chunks->at(i); + } + + // create new chunk for the first one + size_t offset = std::min(this->space(), items.size()); + Chunk newFirstChunk = std::make_shared>(); + newFirstChunk->resize(this->chunks->front()->size() + offset); + + for (size_t i = 0; i < offset; i++) { + newFirstChunk->at(i) = items[items.size() - offset + i]; + acceptedItems.push_back(items[items.size() - offset + i]); + } + + for (size_t i = 0; i < this->chunks->at(0)->size(); i++) { + newFirstChunk->at(i + offset) = this->chunks->at(0)->at(i); + } + + newChunks->at(0) = newFirstChunk; + + this->chunks = newChunks; + qDebug() << acceptedItems.size(); + qDebug() << this->chunks->at(0)->size(); + + if (this->chunks->size() == 1) { + this->lastChunkEnd += offset; + } + } + + return acceptedItems; + } + + // void insertAfter(const std::vector &items, const T &index) + messages::LimitedQueueSnapshot getSnapshot() { std::lock_guard lock(this->mutex); - if (this->vector->size() < this->limit) { - return LimitedQueueSnapshot(this->vector, this->offset, this->vector->size()); - } else { - return LimitedQueueSnapshot(this->vector, this->offset, this->limit); - } + return LimitedQueueSnapshot(this->chunks, this->limit - this->space(), + this->firstChunkOffset, this->lastChunkEnd); } private: - std::shared_ptr> vector; + size_t space() + { + size_t totalSize = 0; + for (Chunk &chunk : *this->chunks) { + totalSize += chunk->size(); + } + + totalSize -= this->chunks->back()->size() - this->lastChunkEnd; + if (this->chunks->size() != 1) { + totalSize -= this->firstChunkOffset; + } + + return this->limit - totalSize; + } + + bool deleteFirstItem(T &deleted) + { + // determine if the first chunk should be deleted + if (space() > 0) { + return false; + } + + deleted = this->chunks->front()->at(this->firstChunkOffset); + + this->firstChunkOffset++; + + // need to delete the first chunk + if (this->firstChunkOffset == this->chunks->front()->size() - 1) { + // copy the chunk vector + ChunkVector newVector = + std::make_shared>>>(); + + // delete first chunk + bool first = true; + for (Chunk &chunk : *this->chunks) { + if (!first) { + newVector->push_back(chunk); + } + first = false; + } + + this->chunks = newVector; + this->firstChunkOffset = 0; + } + return true; + } + + ChunkVector chunks; std::mutex mutex; - unsigned int offset; - unsigned int limit; - unsigned int buffer; + size_t firstChunkOffset; + size_t lastChunkEnd; + size_t limit; + + const size_t chunkSize = 100; }; } // namespace messages diff --git a/src/messages/limitedqueuesnapshot.hpp b/src/messages/limitedqueuesnapshot.hpp index aa2fffcc..6b9ae77a 100644 --- a/src/messages/limitedqueuesnapshot.hpp +++ b/src/messages/limitedqueuesnapshot.hpp @@ -10,29 +10,46 @@ template class LimitedQueueSnapshot { public: - LimitedQueueSnapshot(std::shared_ptr> _vector, std::size_t _offset, - std::size_t _size) - : vector(_vector) - , offset(_offset) - , length(_size) + LimitedQueueSnapshot(std::shared_ptr>>> _chunks, + size_t _length, size_t _firstChunkOffset, size_t _lastChunkEnd) + : chunks(_chunks) + , length(_length) + , firstChunkOffset(_firstChunkOffset) + , lastChunkEnd(_lastChunkEnd) { } std::size_t getLength() { - return length; + return this->length; } T const &operator[](std::size_t index) const { - return vector->at(index + offset); + index += this->firstChunkOffset; + + size_t x = 0; + + for (size_t i = 0; i < this->chunks->size(); i++) { + auto &chunk = this->chunks->at(i); + + if (x <= index && x + chunk->size() > index) { + return chunk->at(index - x); + } + x += chunk->size(); + } + + assert(false && "out of range"); + + return this->chunks->at(0)->at(0); } private: - std::shared_ptr> vector; + std::shared_ptr>>> chunks; - std::size_t offset; - std::size_t length; + size_t length; + size_t firstChunkOffset; + size_t lastChunkEnd; }; } // namespace messages diff --git a/src/singletons/ircmanager.cpp b/src/singletons/ircmanager.cpp index 4443980e..07ffa921 100644 --- a/src/singletons/ircmanager.cpp +++ b/src/singletons/ircmanager.cpp @@ -10,7 +10,6 @@ #include "singletons/settingsmanager.hpp" #include "singletons/windowmanager.hpp" #include "twitch/twitchmessagebuilder.hpp" -#include "twitch/twitchparsemessage.hpp" #include "twitch/twitchuser.hpp" #include "util/urlfetch.hpp" @@ -67,6 +66,12 @@ IrcManager::IrcManager(ChannelManager &_channelManager, ResourceManager &_resour &IrcManager::onConnected); QObject::connect(this->readConnection.get(), &Communi::IrcConnection::disconnected, this, &IrcManager::onDisconnected); + + // join and part chats on event + ChannelManager::getInstance().ircJoin.connect( + [this](const QString &name) { this->joinChannel(name); }); + ChannelManager::getInstance().ircPart.connect( + [this](const QString &name) { this->partChannel(name); }); } IrcManager &IrcManager::getInstance() diff --git a/src/twitch/twitchchannel.cpp b/src/twitch/twitchchannel.cpp index b7403bfa..9d203e54 100644 --- a/src/twitch/twitchchannel.cpp +++ b/src/twitch/twitchchannel.cpp @@ -1,6 +1,7 @@ #include "twitchchannel.hpp" #include "debug/log.hpp" #include "singletons/emotemanager.hpp" +#include "twitch/twitchmessagebuilder.hpp" #include "util/urlfetch.hpp" #include @@ -20,8 +21,6 @@ TwitchChannel::TwitchChannel(const QString &channelName) { debug::Log("[TwitchChannel:{}] Opened", this->name); - this->dontAddMessages = true; - this->reloadChannelEmotes(); this->liveStatusTimer = new QTimer; @@ -158,15 +157,22 @@ void TwitchChannel::fetchRecentMessages() static auto readConnection = singletons::IrcManager::getInstance().getReadConnection(); util::twitch::get(genericURL.arg(roomID), QThread::currentThread(), [=](QJsonObject obj) { - this->dontAddMessages = false; auto msgArray = obj.value("messages").toArray(); - if (msgArray.size()) + if (msgArray.size() > 0) { + std::vector messages; + messages.resize(msgArray.size()); + for (int i = 0; i < msgArray.size(); i++) { QByteArray content = msgArray[i].toString().toUtf8(); auto msg = Communi::IrcMessage::fromData(content, readConnection); auto privMsg = static_cast(msg); - singletons::IrcManager::getInstance().privateMessageReceived(privMsg); + + messages::MessageParseArgs args; + twitch::TwitchMessageBuilder builder(this, privMsg, args); + messages.at(i) = builder.parse(); } + this->addMessagesAtStart(messages); + } }); } diff --git a/src/twitch/twitchmessagebuilder.cpp b/src/twitch/twitchmessagebuilder.cpp index a660aa69..11864a19 100644 --- a/src/twitch/twitchmessagebuilder.cpp +++ b/src/twitch/twitchmessagebuilder.cpp @@ -1,8 +1,8 @@ #include "twitchmessagebuilder.hpp" #include "debug/log.hpp" -#include "singletons/resourcemanager.hpp" #include "singletons/emotemanager.hpp" #include "singletons/ircmanager.hpp" +#include "singletons/resourcemanager.hpp" #include "singletons/settingsmanager.hpp" #include "singletons/thememanager.hpp" #include "singletons/windowmanager.hpp" @@ -200,7 +200,8 @@ SharedMessage TwitchMessageBuilder::parse() this->appendWord(Word( QString("x" + string.mid(5)), Word::BitsAmount, MessageColor(bitsColor), - singletons::FontManager::Medium, QString(string.mid(5)), QString("Twitch Cheer"), + singletons::FontManager::Medium, QString(string.mid(5)), + QString("Twitch Cheer"), Link(Link::Url, QString("https://blog.twitch.tv/" "introducing-cheering-celebrate-together-da62af41fac6")))); @@ -230,13 +231,14 @@ SharedMessage TwitchMessageBuilder::parse() textColor = MessageColor(MessageColor::Link); } - this->appendWord(Word(string, Word::Text, textColor, singletons::FontManager::Medium, string, - QString(), link)); + this->appendWord(Word(string, Word::Text, textColor, + singletons::FontManager::Medium, string, QString(), link)); } else { // is emoji this->appendWord(Word(emoteData.image, Word::EmojiImage, emoteData.image->getName(), emoteData.image->getTooltip())); - Word(emoteData.image->getName(), Word::EmojiText, textColor, singletons::FontManager::Medium, - emoteData.image->getName(), emoteData.image->getTooltip()); + Word(emoteData.image->getName(), Word::EmojiText, textColor, + singletons::FontManager::Medium, emoteData.image->getName(), + emoteData.image->getTooltip()); } } @@ -471,7 +473,8 @@ void TwitchMessageBuilder::parseHighlights() } if (doAlert) { - QApplication::alert(singletons::WindowManager::getInstance().getMainWindow().window(), 2500); + QApplication::alert(singletons::WindowManager::getInstance().getMainWindow().window(), + 2500); } } } @@ -482,10 +485,11 @@ void TwitchMessageBuilder::appendModerationButtons() static QString buttonBanTooltip("Ban user"); static QString buttonTimeoutTooltip("Timeout user"); - this->appendWord(Word(singletons::ResourceManager::getInstance().buttonBan, Word::ButtonBan, QString(), - buttonBanTooltip, Link(Link::UserBan, ircMessage->account()))); - this->appendWord(Word(singletons::ResourceManager::getInstance().buttonTimeout, Word::ButtonTimeout, QString(), - buttonTimeoutTooltip, Link(Link::UserTimeout, ircMessage->account()))); + this->appendWord(Word(singletons::ResourceManager::getInstance().buttonBan, Word::ButtonBan, + QString(), buttonBanTooltip, Link(Link::UserBan, ircMessage->account()))); + this->appendWord(Word(singletons::ResourceManager::getInstance().buttonTimeout, + Word::ButtonTimeout, QString(), buttonTimeoutTooltip, + Link(Link::UserTimeout, ircMessage->account()))); } void TwitchMessageBuilder::appendTwitchEmote(const Communi::IrcPrivateMessage *ircMessage, @@ -567,7 +571,8 @@ bool TwitchMessageBuilder::appendEmote(util::EmoteData &emoteData) void TwitchMessageBuilder::parseTwitchBadges() { - const auto &channelResources = singletons::ResourceManager::getInstance().channels[this->roomID]; + const auto &channelResources = + singletons::ResourceManager::getInstance().channels[this->roomID]; auto iterator = this->tags.find("badges"); @@ -602,42 +607,43 @@ void TwitchMessageBuilder::parseTwitchBadges() Word(badgeVersion.badgeImage1x, Word::BadgeVanity, QString(), QString("Twitch " + QString::fromStdString(badgeVersion.title)))); } catch (const std::exception &e) { - debug::Log("Exception caught: {} when trying to fetch badge version {}", + debug::Log("Exception caught: {} when trying to fetch badge version {} ", e.what(), versionKey); } } catch (const std::exception &e) { debug::Log("No badge set with key bits. Exception: {}", e.what()); } } else if (badge == "staff/1") { - appendWord(Word(singletons::ResourceManager::getInstance().badgeStaff, Word::BadgeGlobalAuthority, - QString(), QString("Twitch Staff"))); + appendWord(Word(singletons::ResourceManager::getInstance().badgeStaff, + Word::BadgeGlobalAuthority, QString(), QString("Twitch Staff"))); } else if (badge == "admin/1") { - appendWord(Word(singletons::ResourceManager::getInstance().badgeAdmin, Word::BadgeGlobalAuthority, - QString(), QString("Twitch Admin"))); + appendWord(Word(singletons::ResourceManager::getInstance().badgeAdmin, + Word::BadgeGlobalAuthority, QString(), QString("Twitch Admin"))); } else if (badge == "global_mod/1") { appendWord(Word(singletons::ResourceManager::getInstance().badgeGlobalModerator, Word::BadgeGlobalAuthority, QString(), QString("Global Moderator"))); } else if (badge == "moderator/1") { // TODO: Implement custom FFZ moderator badge - appendWord(Word(singletons::ResourceManager::getInstance().badgeModerator, Word::BadgeChannelAuthority, - QString(), + appendWord(Word(singletons::ResourceManager::getInstance().badgeModerator, + Word::BadgeChannelAuthority, QString(), QString("Channel Moderator"))); // custom badge } else if (badge == "turbo/1") { - appendWord(Word(singletons::ResourceManager::getInstance().badgeTurbo, Word::BadgeVanity, QString(), - QString("Turbo Subscriber"))); + appendWord(Word(singletons::ResourceManager::getInstance().badgeTurbo, + Word::BadgeVanity, QString(), QString("Turbo Subscriber"))); } else if (badge == "broadcaster/1") { - appendWord(Word(singletons::ResourceManager::getInstance().badgeBroadcaster, Word::BadgeChannelAuthority, - QString(), QString("Channel Broadcaster"))); + appendWord(Word(singletons::ResourceManager::getInstance().badgeBroadcaster, + Word::BadgeChannelAuthority, QString(), + QString("Channel Broadcaster"))); } else if (badge == "premium/1") { - appendWord(Word(singletons::ResourceManager::getInstance().badgePremium, Word::BadgeVanity, QString(), - QString("Twitch Prime"))); + appendWord(Word(singletons::ResourceManager::getInstance().badgePremium, + Word::BadgeVanity, QString(), QString("Twitch Prime"))); } else if (badge.startsWith("partner/")) { int index = badge.midRef(8).toInt(); switch (index) { case 1: { - appendWord(Word(singletons::ResourceManager::getInstance().badgeVerified, Word::BadgeVanity, - QString(), "Twitch Verified")); + appendWord(Word(singletons::ResourceManager::getInstance().badgeVerified, + Word::BadgeVanity, QString(), "Twitch Verified")); } break; default: { printf("[TwitchMessageBuilder] Unhandled partner badge index: %d\n", index); @@ -695,7 +701,8 @@ void TwitchMessageBuilder::parseTwitchBadges() std::string versionKey = parts[1].toStdString(); try { - auto &badgeSet = singletons::ResourceManager::getInstance().badgeSets.at(badgeSetKey); + auto &badgeSet = + singletons::ResourceManager::getInstance().badgeSets.at(badgeSetKey); try { auto &badgeVersion = badgeSet.versions.at(versionKey); diff --git a/src/twitch/twitchparsemessage.cpp b/src/twitch/twitchparsemessage.cpp deleted file mode 100644 index f784f824..00000000 --- a/src/twitch/twitchparsemessage.cpp +++ /dev/null @@ -1,349 +0,0 @@ -//#include "twitchparsemessage.hpp" -//#include "colorscheme.hpp" -//#include "emojis.hpp" -//#include "singletons/emotemanager.hpp" -//#include "singletons/ircmanager.hpp" -//#include "resources.hpp" -//#include "twitch/twitchmessagebuilder.hpp" -// -//#include -// -// using namespace chatterino::messages; -// -// namespace chatterino { -// namespace twitch { -// SharedMessage -// twitchParseMessage(const Communi::IrcPrivateMessage *ircMessage, -// Channel *channel, const MessageParseArgs &args) -//{ -// TwitchMessageBuilder b; -// -// // timestamp -// b.appendTimestamp(); -// -// auto tags = ircMessage->tags(); -// -// auto iterator = tags.find("id"); -// -// if (iterator != tags.end()) { -// b.messageId = iterator.value().toString(); -// } -// -// // timestamps -// iterator = tags.find("tmi-sent-ts"); -// -// // mod buttons -// static QString buttonBanTooltip("Ban user"); -// static QString buttonTimeoutTooltip("Timeout user"); -// -// b.appendWord(Word(Resources::getButtonBan(), Word::ButtonBan, QString(), -// buttonBanTooltip, -// Link(Link::UserBan, ircMessage->account()))); -// b.appendWord(Word(Resources::getButtonTimeout(), Word::ButtonTimeout, -// QString(), buttonTimeoutTooltip, -// Link(Link::UserTimeout, ircMessage->account()))); -// -// // badges -// iterator = tags.find("badges"); -// -// if (iterator != tags.end()) { -// auto badges = iterator.value().toString().split(','); -// -// b.appendTwitchBadges(badges); -// } -// -// // color -// QColor usernameColor = ColorScheme::getInstance().SystemMessageColor; -// -// iterator = tags.find("color"); -// if (iterator != tags.end()) { -// usernameColor = QColor(iterator.value().toString()); -// } -// -// // channel name -// if (args.includeChannelName) { -// QString channelName("#" + channel->getName()); -// b.appendWord( -// Word(channelName, Word::Misc, -// ColorScheme::getInstance().SystemMessageColor, -// QString(channelName), QString(), -// Link(Link::Url, channel->getName() + "\n" + b.messageId))); -// } -// -// // username -// b.userName = ircMessage->nick(); -// -// if (b.userName.isEmpty()) { -// b.userName = tags.value(QLatin1String("login")).toString(); -// } -// -// QString displayName; -// -// iterator = tags.find("display-name"); -// if (iterator == tags.end()) { -// displayName = ircMessage->account(); -// } else { -// displayName = iterator.value().toString(); -// } -// -// bool hasLocalizedName = -// QString::compare(displayName, ircMessage->account()) == 0; -// QString userDisplayString = -// displayName + -// (hasLocalizedName ? (" (" + ircMessage->account() + ")") : QString()); -// -// if (args.isSentWhisper) { -// userDisplayString += -// IrcManager::getInstance().getUser().getUserName() + " -> "; -// } -// -// if (args.isReceivedWhisper) { -// userDisplayString += -// " -> " + IrcManager::getInstance().getUser().getUserName(); -// } -// -// if (!ircMessage->isAction()) { -// userDisplayString += ": "; -// } -// -// b.appendWord(Word(userDisplayString, Word::Username, usernameColor, -// userDisplayString, QString())); -// -// // highlights -// // TODO: implement this xD -// -// // bits -// QString bits = ""; -// -// iterator = tags.find("bits"); -// if (iterator != tags.end()) { -// bits = iterator.value().toString(); -// } -// -// // twitch emotes -// std::vector> twitchEmotes; -// -// iterator = tags.find("emotes"); -// -// if (iterator != tags.end()) { -// auto emotes = iterator.value().toString().split('/'); -// -// for (QString emote : emotes) { -// if (!emote.contains(':')) -// continue; -// -// QStringList parameters = emote.split(':'); -// -// if (parameters.length() < 2) -// continue; -// -// long int id = std::stol(parameters.at(0).toStdString(), nullptr, 10); -// -// QStringList occurences = parameters.at(1).split(','); -// -// for (QString occurence : occurences) { -// QStringList coords = occurence.split('-'); -// -// if (coords.length() < 2) -// continue; -// -// long int start = -// std::stol(coords.at(0).toStdString(), nullptr, 10); -// long int end = std::stol(coords.at(1).toStdString(), nullptr, -// 10); -// -// if (start >= end || start < 0 || -// end > ircMessage->content().length()) -// continue; -// -// QString name = -// ircMessage->content().mid(start, end - start + 1); -// -// twitchEmotes.push_back(std::pair( -// start, -// EmoteManager::getInstance().getTwitchEmoteById(name, -// id))); -// } -// } -// -// std::sort(twitchEmotes.begin(), twitchEmotes.end(), sortTwitchEmotes); -// } -// -// auto currentTwitchEmote = twitchEmotes.begin(); -// -// // words -// QColor textColor = ircMessage->isAction() ? usernameColor -// : -// ColorScheme::getInstance().Text; -// -// QStringList splits = ircMessage->content().split(' '); -// -// long int i = 0; -// -// for (QString split : splits) { -// // twitch emote -// if (currentTwitchEmote != twitchEmotes.end() && -// currentTwitchEmote->first == i) { -// b.appendWord(Word(currentTwitchEmote->second, -// Word::TwitchEmoteImage, -// currentTwitchEmote->second->getName(), -// currentTwitchEmote->second->getName() + -// QString("\nTwitch Emote"))); -// b.appendWord(Word(currentTwitchEmote->second->getName(), -// Word::TwitchEmoteText, textColor, -// currentTwitchEmote->second->getName(), -// currentTwitchEmote->second->getName() + -// QString("\nTwitch Emote"))); -// -// i += split.length() + 1; -// currentTwitchEmote = std::next(currentTwitchEmote); -// -// continue; -// } -// -// // split words -// std::vector> parsed; -// -// Emojis::parseEmojis(parsed, split); -// -// for (const std::tuple &tuple : parsed) { -// LazyLoadedImage *image = std::get<0>(tuple); -// -// if (image == nullptr) { // is text -// QString string = std::get<1>(tuple); -// -// static QRegularExpression cheerRegex("cheer[1-9][0-9]*"); -// -// // cheers -// if (!bits.isEmpty() && string.length() >= 6 && -// cheerRegex.match(string).isValid()) { -// auto cheer = string.mid(5).toInt(); -// -// QString color; -// -// QColor bitsColor; -// -// if (cheer >= 10000) { -// color = "red"; -// bitsColor = QColor::fromHslF(0, 1, 0.5); -// } else if (cheer >= 5000) { -// color = "blue"; -// bitsColor = QColor::fromHslF(0.61, 1, 0.4); -// } else if (cheer >= 1000) { -// color = "green"; -// bitsColor = QColor::fromHslF(0.5, 1, 0.5); -// } else if (cheer >= 100) { -// color = "purple"; -// bitsColor = QColor::fromHslF(0.8, 1, 0.5); -// } else { -// color = "gray"; -// bitsColor = QColor::fromHslF(0.5f, 0.5f, 0.5f); -// } -// -// QString bitsLinkAnimated = QString( -// "http://static-cdn.jtvnw.net/bits/dark/animated/" + -// color + "/1"); -// QString bitsLink = QString( -// "http://static-cdn.jtvnw.net/bits/dark/static/" + -// color + "/1"); -// -// LazyLoadedImage *imageAnimated = -// EmoteManager::getInstance() -// .getMiscImageFromCache() -// .getOrAdd(bitsLinkAnimated, [&bitsLinkAnimated] { -// return new LazyLoadedImage(bitsLinkAnimated); -// }); -// LazyLoadedImage *image = -// EmoteManager::getInstance() -// .getMiscImageFromCache() -// .getOrAdd(bitsLink, [&bitsLink] { -// return new LazyLoadedImage(bitsLink); -// }); -// -// b.appendWord( -// Word(imageAnimated, Word::BitsAnimated, -// QString("cheer"), QString("Twitch Cheer"), -// Link(Link::Url, -// QString("https://blog.twitch.tv/" -// "introducing-cheering-celebrate-" -// "together-da62af41fac6")))); -// b.appendWord( -// Word(image, Word::BitsStatic, QString("cheer"), -// QString("Twitch Cheer"), -// Link(Link::Url, -// QString("https://blog.twitch.tv/" -// "introducing-cheering-celebrate-" -// "together-da62af41fac6")))); -// -// b.appendWord( -// Word(QString("x" + string.mid(5)), Word::BitsAmount, -// bitsColor, QString(string.mid(5)), -// QString("Twitch Cheer"), -// Link(Link::Url, -// QString("https://blog.twitch.tv/" -// "introducing-cheering-celebrate-" -// "together-da62af41fac6")))); -// -// continue; -// } -// -// // bttv / ffz emotes -// LazyLoadedImage *bttvEmote; -// -// // TODO: Implement this (ignored emotes) -// if (EmoteManager::getInstance().getBttvEmotes().tryGet( -// string, bttvEmote) || -// channel->getBttvChannelEmotes().tryGet(string, bttvEmote) -// || -// EmoteManager::getInstance().getFfzEmotes().tryGet( -// string, bttvEmote) || -// channel->getFfzChannelEmotes().tryGet(string, bttvEmote) -// || -// EmoteManager::getInstance().getChatterinoEmotes().tryGet( -// string, bttvEmote)) { -// b.appendWord(Word(bttvEmote, Word::BttvEmoteImage, -// bttvEmote->getName(), -// bttvEmote->getTooltip(), -// Link(Link::Url, bttvEmote->getUrl()))); -// -// continue; -// } -// -// // actually just a word -// QString link = b.matchLink(string); -// -// b.appendWord( -// Word(string, Word::Text, textColor, string, QString(), -// link.isEmpty() ? Link() : Link(Link::Url, link))); -// } else { // is emoji -// static QString emojiTooltip("Emoji"); -// -// b.appendWord(Word(image, Word::EmojiImage, image->getName(), -// emojiTooltip)); -// Word(image->getName(), Word::EmojiText, textColor, -// image->getName(), emojiTooltip); -// } -// } -// -// i += split.length() + 1; -// } -// -// // TODO: Implement this xD -// // if (!isReceivedWhisper && -// // AppSettings.HighlightIgnoredUsers.ContainsKey(Username)) -// // { -// // HighlightTab = false; -// // } -// -// return b.build(); -//} -// -// bool -// sortTwitchEmotes(const std::pair &a, -// const std::pair &b) -//{ -// return a.first < b.first; -//} -//} -//} -// diff --git a/src/twitch/twitchparsemessage.hpp b/src/twitch/twitchparsemessage.hpp deleted file mode 100644 index c4227d8f..00000000 --- a/src/twitch/twitchparsemessage.hpp +++ /dev/null @@ -1,13 +0,0 @@ -//#ifndef MESSAGEPARSER_H -//#define MESSAGEPARSER_H -// -//#include "messages/lazyloadedimage.hpp" -//#include "messages/messagebuilder.hpp" -//#include "messages/messageparseargs.hpp" -// -// namespace chatterino { -// namespace twitch { -//} -//} -// -//#endif // MESSAGEPARSER_H diff --git a/src/widgets/helper/channelview.cpp b/src/widgets/helper/channelview.cpp index 0392c2ff..abe66fd7 100644 --- a/src/widgets/helper/channelview.cpp +++ b/src/widgets/helper/channelview.cpp @@ -361,7 +361,7 @@ void ChannelView::setChannel(std::shared_ptr newChannel) auto messageRef = new MessageRef(message); - if (this->messages.appendItem(SharedMessageRef(messageRef), deleted)) { + if (this->messages.pushBack(SharedMessageRef(messageRef), deleted)) { if (this->scrollBar.isAtBottom()) { this->scrollBar.scrollToBottom(); } else { @@ -374,7 +374,26 @@ void ChannelView::setChannel(std::shared_ptr newChannel) } layoutMessages(); - update(); + }); + + this->messageAddedAtStartConnection = + newChannel->messagesAddedAtStart.connect([this](std::vector &messages) { + std::vector messageRefs; + messageRefs.resize(messages.size()); + qDebug() << messages.size(); + for (int i = 0; i < messages.size(); i++) { + messageRefs.at(i) = SharedMessageRef(new MessageRef(messages.at(i))); + } + + if (this->messages.pushFront(messageRefs).size() > 0) { + if (this->scrollBar.isAtBottom()) { + this->scrollBar.scrollToBottom(); + } else { + this->scrollBar.offset((qreal)-messages.size()); + } + } + + layoutMessages(); }); // on message removed @@ -395,7 +414,7 @@ void ChannelView::setChannel(std::shared_ptr newChannel) auto messageRef = new MessageRef(snapshot[i]); - this->messages.appendItem(SharedMessageRef(messageRef), deleted); + this->messages.pushBack(SharedMessageRef(messageRef), deleted); } this->channel = newChannel; @@ -551,15 +570,14 @@ void ChannelView::drawMessages(QPainter &painter) // remove messages that are on screen // the messages that are left at the end get their buffers reset for (size_t i = start; i < messagesSnapshot.getLength(); ++i) { - messages::MessageRef *messageRef = messagesSnapshot[i].get(); - auto it = this->messagesOnScreen.find(messageRef); + auto it = this->messagesOnScreen.find(messagesSnapshot[i]); if (it != this->messagesOnScreen.end()) { this->messagesOnScreen.erase(it); } } // delete the message buffers that aren't on screen - for (MessageRef *item : this->messagesOnScreen) { + for (std::shared_ptr item : this->messagesOnScreen) { item->buffer.reset(); } @@ -567,11 +585,11 @@ void ChannelView::drawMessages(QPainter &painter) // add all messages on screen to the map for (size_t i = start; i < messagesSnapshot.getLength(); ++i) { - messages::MessageRef *messageRef = messagesSnapshot[i].get(); + std::shared_ptr messageRef = messagesSnapshot[i]; this->messagesOnScreen.insert(messageRef); - if (messageRef == end) { + if (messageRef.get() == end) { break; } } diff --git a/src/widgets/helper/channelview.hpp b/src/widgets/helper/channelview.hpp index 90350ff7..8f3a40ce 100644 --- a/src/widgets/helper/channelview.hpp +++ b/src/widgets/helper/channelview.hpp @@ -164,13 +164,14 @@ private: messages::LimitedQueue messages; boost::signals2::connection messageAppendedConnection; + boost::signals2::connection messageAddedAtStartConnection; boost::signals2::connection messageRemovedConnection; boost::signals2::connection repaintGifsConnection; boost::signals2::connection layoutConnection; std::vector managedConnections; - std::unordered_set messagesOnScreen; + std::unordered_set> messagesOnScreen; private slots: void wordTypeMaskChanged()