diff --git a/.clang-format b/.clang-format index bd3f2c430..eeb9eb929 100644 --- a/.clang-format +++ b/.clang-format @@ -25,6 +25,9 @@ DerivePointerBinding: false FixNamespaceComments: true IndentCaseLabels: true IndentWidth: 4 +IndentWrappedFunctionNames: true +IndentPPDirectives: AfterHash +NamespaceIndentation: Inner PointerBindsToType: false SpacesBeforeTrailingComments: 2 Standard: Auto diff --git a/chatterino.pro b/chatterino.pro index 7ddf1fd34..dd97cb271 100644 --- a/chatterino.pro +++ b/chatterino.pro @@ -33,7 +33,7 @@ equals(QMAKE_CXX, "clang++")|equals(QMAKE_CXX, "g++") { } # Icons -macx:ICON = resources/images/chatterino2.icns +#macx:ICON = resources/images/chatterino2.icns win32:RC_FILE = resources/windows.rc @@ -71,6 +71,8 @@ win32 { # OSX include directory macx { INCLUDEPATH += /usr/local/include + INCLUDEPATH += /usr/local/opt/openssl/include + LIBS += -L/usr/local/opt/openssl/lib } # Optional dependency on Windows SDK 7 @@ -250,7 +252,8 @@ SOURCES += \ src/widgets/helper/EffectLabel.cpp \ src/widgets/helper/Button.cpp \ src/messages/MessageContainer.cpp \ - src/debug/Benchmark.cpp + src/debug/Benchmark.cpp \ + src/common/UsernameSet.cpp HEADERS += \ src/Application.hpp \ @@ -445,7 +448,8 @@ HEADERS += \ src/widgets/helper/EffectLabel.hpp \ src/util/LayoutHelper.hpp \ src/widgets/helper/Button.hpp \ - src/messages/MessageContainer.hpp + src/messages/MessageContainer.hpp \ + src/common/UsernameSet.hpp RESOURCES += \ resources/resources.qrc \ diff --git a/src/Application.cpp b/src/Application.cpp index 85b4f382c..68ccc78f8 100644 --- a/src/Application.cpp +++ b/src/Application.cpp @@ -6,11 +6,13 @@ #include "controllers/ignores/IgnoreController.hpp" #include "controllers/moderationactions/ModerationActions.hpp" #include "controllers/taggedusers/TaggedUsersController.hpp" +#include "debug/Log.hpp" #include "messages/MessageBuilder.hpp" #include "providers/bttv/BttvEmotes.hpp" #include "providers/ffz/FfzEmotes.hpp" #include "providers/twitch/PubsubClient.hpp" #include "providers/twitch/TwitchServer.hpp" +#include "singletons/Emotes.hpp" #include "singletons/Fonts.hpp" #include "singletons/Logging.hpp" #include "singletons/NativeMessaging.hpp" @@ -21,6 +23,7 @@ #include "singletons/WindowManager.hpp" #include "util/IsBigEndian.hpp" #include "util/PostToThread.hpp" +#include "widgets/Window.hpp" #include @@ -35,9 +38,7 @@ Application *Application::instance = nullptr; // to each other Application::Application(Settings &_settings, Paths &_paths) - : settings(&_settings) - , paths(&_paths) - , resources(&this->emplace()) + : resources(&this->emplace()) , themes(&this->emplace()) , fonts(&this->emplace()) @@ -98,15 +99,15 @@ void Application::save() void Application::initNm() { #ifdef Q_OS_WIN -#ifdef QT_DEBUG -#ifdef C_DEBUG_NM +# ifdef QT_DEBUG +# ifdef C_DEBUG_NM this->nativeMessaging->registerHost(); this->nativeMessaging->openGuiMessageQueue(); -#endif -#else +# endif +# else this->nativeMessaging->registerHost(); this->nativeMessaging->openGuiMessageQueue(); -#endif +# endif #endif } diff --git a/src/Application.hpp b/src/Application.hpp index 2a62503bf..6089aef43 100644 --- a/src/Application.hpp +++ b/src/Application.hpp @@ -1,7 +1,6 @@ #pragma once #include "common/Singleton.hpp" -#include "singletons/Resources.hpp" #include #include @@ -26,7 +25,7 @@ class AccountManager; class Emotes; class Settings; class Fonts; -class Resources; +class Resources2; class Application { @@ -47,8 +46,6 @@ public: friend void test(); - Settings *const settings{}; - Paths *const paths{}; Resources2 *const resources; Theme *const themes{}; diff --git a/src/BrowserExtension.cpp b/src/BrowserExtension.cpp index 5275e0648..f12d19b5e 100644 --- a/src/BrowserExtension.cpp +++ b/src/BrowserExtension.cpp @@ -9,31 +9,31 @@ #include #ifdef Q_OS_WIN -#include -#include -#include +# include +# include +# include #endif namespace chatterino { namespace { -void initFileMode() -{ + void initFileMode() + { #ifdef Q_OS_WIN - _setmode(_fileno(stdin), _O_BINARY); - _setmode(_fileno(stdout), _O_BINARY); + _setmode(_fileno(stdin), _O_BINARY); + _setmode(_fileno(stdout), _O_BINARY); #endif -} + } -void runLoop(NativeMessagingClient &client) -{ - while (true) { - char size_c[4]; - std::cin.read(size_c, 4); + void runLoop(NativeMessagingClient &client) + { + while (true) { + char size_c[4]; + std::cin.read(size_c, 4); - if (std::cin.eof()) break; + if (std::cin.eof()) break; - auto size = *reinterpret_cast(size_c); + auto size = *reinterpret_cast(size_c); #if 0 bool bigEndian = isBigEndian(); @@ -48,14 +48,14 @@ void runLoop(NativeMessagingClient &client) } #endif - std::unique_ptr buffer(new char[size + 1]); - std::cin.read(buffer.get(), size); - *(buffer.get() + size) = '\0'; + std::unique_ptr buffer(new char[size + 1]); + std::cin.read(buffer.get(), size); + *(buffer.get() + size) = '\0'; - client.sendMessage( - QByteArray::fromRawData(buffer.get(), static_cast(size))); + client.sendMessage(QByteArray::fromRawData( + buffer.get(), static_cast(size))); + } } -} } // namespace bool shouldRunBrowserExtensionHost(const QStringList &args) diff --git a/src/PrecompiledHeader.hpp b/src/PrecompiledHeader.hpp index 6eec78c8e..10dfcd418 100644 --- a/src/PrecompiledHeader.hpp +++ b/src/PrecompiledHeader.hpp @@ -1,168 +1,168 @@ #ifdef __cplusplus -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include -#ifndef UNUSED -#define UNUSED(x) (void)(x) -#endif +# ifndef UNUSED +# define UNUSED(x) (void)(x) +# endif -#ifndef ATTR_UNUSED -#ifdef Q_OS_WIN -#define ATTR_UNUSED -#else -#define ATTR_UNUSED __attribute__((unused)) -#endif -#endif +# ifndef ATTR_UNUSED +# ifdef Q_OS_WIN +# define ATTR_UNUSED +# else +# define ATTR_UNUSED __attribute__((unused)) +# endif +# endif #endif diff --git a/src/RunGui.cpp b/src/RunGui.cpp index 9fdcdb20c..e342ca7c1 100644 --- a/src/RunGui.cpp +++ b/src/RunGui.cpp @@ -12,7 +12,7 @@ #include "widgets/dialogs/LastRunCrashDialog.hpp" #ifdef C_USE_BREAKPAD -#include +# include #endif // void initQt(); @@ -23,80 +23,82 @@ namespace chatterino { namespace { -void installCustomPalette() -{ - // borrowed from - // https://stackoverflow.com/questions/15035767/is-the-qt-5-dark-fusion-theme-available-for-windows - auto dark = qApp->palette(); + void installCustomPalette() + { + // borrowed from + // https://stackoverflow.com/questions/15035767/is-the-qt-5-dark-fusion-theme-available-for-windows + auto dark = qApp->palette(); - dark.setColor(QPalette::Window, QColor(22, 22, 22)); - dark.setColor(QPalette::WindowText, Qt::white); - dark.setColor(QPalette::Text, Qt::white); - dark.setColor(QPalette::Disabled, QPalette::WindowText, - QColor(127, 127, 127)); - dark.setColor(QPalette::Base, QColor("#333")); - dark.setColor(QPalette::AlternateBase, QColor("#444")); - dark.setColor(QPalette::ToolTipBase, Qt::white); - dark.setColor(QPalette::ToolTipText, Qt::white); - dark.setColor(QPalette::Disabled, QPalette::Text, QColor(127, 127, 127)); - dark.setColor(QPalette::Dark, QColor(35, 35, 35)); - dark.setColor(QPalette::Shadow, QColor(20, 20, 20)); - dark.setColor(QPalette::Button, QColor(70, 70, 70)); - dark.setColor(QPalette::ButtonText, Qt::white); - dark.setColor(QPalette::Disabled, QPalette::ButtonText, - QColor(127, 127, 127)); - dark.setColor(QPalette::BrightText, Qt::red); - dark.setColor(QPalette::Link, QColor(42, 130, 218)); - dark.setColor(QPalette::Highlight, QColor(42, 130, 218)); - dark.setColor(QPalette::Disabled, QPalette::Highlight, QColor(80, 80, 80)); - dark.setColor(QPalette::HighlightedText, Qt::white); - dark.setColor(QPalette::Disabled, QPalette::HighlightedText, - QColor(127, 127, 127)); + dark.setColor(QPalette::Window, QColor(22, 22, 22)); + dark.setColor(QPalette::WindowText, Qt::white); + dark.setColor(QPalette::Text, Qt::white); + dark.setColor(QPalette::Disabled, QPalette::WindowText, + QColor(127, 127, 127)); + dark.setColor(QPalette::Base, QColor("#333")); + dark.setColor(QPalette::AlternateBase, QColor("#444")); + dark.setColor(QPalette::ToolTipBase, Qt::white); + dark.setColor(QPalette::ToolTipText, Qt::white); + dark.setColor(QPalette::Disabled, QPalette::Text, + QColor(127, 127, 127)); + dark.setColor(QPalette::Dark, QColor(35, 35, 35)); + dark.setColor(QPalette::Shadow, QColor(20, 20, 20)); + dark.setColor(QPalette::Button, QColor(70, 70, 70)); + dark.setColor(QPalette::ButtonText, Qt::white); + dark.setColor(QPalette::Disabled, QPalette::ButtonText, + QColor(127, 127, 127)); + dark.setColor(QPalette::BrightText, Qt::red); + dark.setColor(QPalette::Link, QColor(42, 130, 218)); + dark.setColor(QPalette::Highlight, QColor(42, 130, 218)); + dark.setColor(QPalette::Disabled, QPalette::Highlight, + QColor(80, 80, 80)); + dark.setColor(QPalette::HighlightedText, Qt::white); + dark.setColor(QPalette::Disabled, QPalette::HighlightedText, + QColor(127, 127, 127)); - qApp->setPalette(dark); -} - -void initQt() -{ - // set up the QApplication flags - QApplication::setAttribute(Qt::AA_Use96Dpi, true); -#ifdef Q_OS_WIN32 - QApplication::setAttribute(Qt::AA_DisableHighDpiScaling, true); -#endif - - QApplication::setStyle(QStyleFactory::create("Fusion")); - - installCustomPalette(); -} - -void showLastCrashDialog() -{ -#ifndef C_DISABLE_CRASH_DIALOG - LastRunCrashDialog dialog; - - switch (dialog.exec()) { - case QDialog::Accepted: { - }; break; - default: { - _exit(0); - } + qApp->setPalette(dark); } + + void initQt() + { + // set up the QApplication flags + QApplication::setAttribute(Qt::AA_Use96Dpi, true); +#ifdef Q_OS_WIN32 + QApplication::setAttribute(Qt::AA_DisableHighDpiScaling, true); #endif -} -void createRunningFile(const QString &path) -{ - QFile runningFile(path); + QApplication::setStyle(QStyleFactory::create("Fusion")); - runningFile.open(QIODevice::WriteOnly | QIODevice::Truncate); - runningFile.flush(); - runningFile.close(); -} + installCustomPalette(); + } -void removeRunningFile(const QString &path) -{ - QFile::remove(path); -} + void showLastCrashDialog() + { +#ifndef C_DISABLE_CRASH_DIALOG + LastRunCrashDialog dialog; + + switch (dialog.exec()) { + case QDialog::Accepted: { + }; break; + default: { + _exit(0); + } + } +#endif + } + + void createRunningFile(const QString &path) + { + QFile runningFile(path); + + runningFile.open(QIODevice::WriteOnly | QIODevice::Truncate); + runningFile.flush(); + runningFile.close(); + } + + void removeRunningFile(const QString &path) + { + QFile::remove(path); + } } // namespace void runGui(QApplication &a, Paths &paths, Settings &settings) @@ -107,7 +109,7 @@ void runGui(QApplication &a, Paths &paths, Settings &settings) chatterino::Updates::getInstance().checkForUpdates(); #ifdef C_USE_BREAKPAD - QBreakpadInstance.setDumpPath(app->paths->settingsFolderPath + "/Crashes"); + QBreakpadInstance.setDumpPath(getPaths()->settingsFolderPath + "/Crashes"); #endif // Running file diff --git a/src/common/Aliases.hpp b/src/common/Aliases.hpp index 8a61a9639..2dea876b7 100644 --- a/src/common/Aliases.hpp +++ b/src/common/Aliases.hpp @@ -32,3 +32,5 @@ QStringAlias(UserName); QStringAlias(UserId); QStringAlias(Url); QStringAlias(Tooltip); +QStringAlias(EmoteId); +QStringAlias(EmoteName); diff --git a/src/common/Channel.cpp b/src/common/Channel.cpp index d7f1659f4..f9ac3ac50 100644 --- a/src/common/Channel.cpp +++ b/src/common/Channel.cpp @@ -22,15 +22,10 @@ namespace chatterino { // Channel // Channel::Channel(const QString &name, Type type) - : completionModel(name) + : completionModel(*this) , name_(name) , type_(type) { - QObject::connect(&this->clearCompletionModelTimer_, &QTimer::timeout, - [this]() { - this->completionModel.clearExpiredStrings(); // - }); - this->clearCompletionModelTimer_.start(60 * 1000); } Channel::~Channel() @@ -165,13 +160,14 @@ void Channel::disableAllMessages() LimitedQueueSnapshot snapshot = this->getMessageSnapshot(); int snapshotLength = snapshot.getLength(); for (int i = 0; i < snapshotLength; i++) { - auto &s = snapshot[i]; - if (s->flags.hasAny({MessageFlag::System, MessageFlag::Timeout})) { + auto &message = snapshot[i]; + if (message->flags.hasAny( + {MessageFlag::System, MessageFlag::Timeout})) { continue; } // FOURTF: disabled for now - // s->flags.EnableFlag(MessageFlag::Disabled); + const_cast(message.get())->flags.set(MessageFlag::Disabled); } } diff --git a/src/common/Channel.hpp b/src/common/Channel.hpp index cb93c1694..f265f4b70 100644 --- a/src/common/Channel.hpp +++ b/src/common/Channel.hpp @@ -1,9 +1,7 @@ #pragma once #include "common/CompletionModel.hpp" -#include "messages/Image.hpp" #include "messages/LimitedQueue.hpp" -#include "messages/Message.hpp" #include #include @@ -12,7 +10,9 @@ #include namespace chatterino { + struct Message; +using MessagePtr = std::shared_ptr; class Channel : public std::enable_shared_from_this { @@ -51,7 +51,6 @@ public: void addOrReplaceTimeout(MessagePtr message); void disableAllMessages(); void replaceMessage(MessagePtr message, MessagePtr replacement); - virtual void addRecentChatter(const MessagePtr &message); QStringList modList; @@ -67,6 +66,7 @@ public: protected: virtual void onConnected(); + virtual void addRecentChatter(const MessagePtr &message); private: const QString name_; diff --git a/src/common/Common.hpp b/src/common/Common.hpp index 251727c24..7f22bf27d 100644 --- a/src/common/Common.hpp +++ b/src/common/Common.hpp @@ -3,7 +3,6 @@ #include "common/Aliases.hpp" #include "common/Outcome.hpp" #include "common/ProviderId.hpp" -#include "debug/Log.hpp" #include #include @@ -39,4 +38,12 @@ std::weak_ptr weakOf(T *element) return element->shared_from_this(); } +struct Message; +using MessagePtr = std::shared_ptr; + +enum class CopyMode { + Everything, + OnlyTextAndEmotes, +}; + } // namespace chatterino diff --git a/src/common/CompletionModel.cpp b/src/common/CompletionModel.cpp index 9e2f4d024..8ccafb8e5 100644 --- a/src/common/CompletionModel.cpp +++ b/src/common/CompletionModel.cpp @@ -2,45 +2,30 @@ #include "Application.hpp" #include "common/Common.hpp" +#include "common/UsernameSet.hpp" #include "controllers/accounts/AccountController.hpp" #include "controllers/commands/CommandController.hpp" +#include "debug/Benchmark.hpp" #include "debug/Log.hpp" +#include "providers/twitch/TwitchChannel.hpp" #include "providers/twitch/TwitchServer.hpp" #include "singletons/Emotes.hpp" #include - #include namespace chatterino { -// -- TaggedString +// +// TaggedString +// -CompletionModel::TaggedString::TaggedString(const QString &_str, Type _type) - : str(_str) +CompletionModel::TaggedString::TaggedString(const QString &_string, Type _type) + : string(_string) , type(_type) - , timeAdded(std::chrono::steady_clock::now()) { } -bool CompletionModel::TaggedString::isExpired( - const std::chrono::steady_clock::time_point &now) const -{ - switch (this->type) { - case Type::Username: { - static std::chrono::minutes expirationTimer(10); - - return (this->timeAdded + expirationTimer < now); - } break; - - default: { - return false; - } break; - } - - return false; -} - bool CompletionModel::TaggedString::isEmote() const { return this->type > Type::EmoteStart && this->type < Type::EmoteEnd; @@ -48,35 +33,23 @@ bool CompletionModel::TaggedString::isEmote() const bool CompletionModel::TaggedString::operator<(const TaggedString &that) const { - if (this->isEmote()) { - if (that.isEmote()) { - int k = QString::compare(this->str, that.str, Qt::CaseInsensitive); - if (k == 0) { - return this->str > that.str; - } - - return k < 0; - } - - return true; + if (this->isEmote() != that.isEmote()) { + return this->isEmote(); } - if (that.isEmote()) { - return false; - } - - int k = QString::compare(this->str, that.str, Qt::CaseInsensitive); - if (k == 0) { - return false; - } + // try comparing insensitively, if they are the same then senstively + // (fixes order of LuL and LUL) + int k = QString::compare(this->string, that.string, Qt::CaseInsensitive); + if (k == 0) return this->string > that.string; return k < 0; } -// -- CompletionModel - -CompletionModel::CompletionModel(const QString &channelName) - : channelName_(channelName) +// +// CompletionModel +// +CompletionModel::CompletionModel(Channel &channel) + : channel_(channel) { } @@ -87,160 +60,90 @@ int CompletionModel::columnCount(const QModelIndex &) const QVariant CompletionModel::data(const QModelIndex &index, int) const { - std::lock_guard lock(this->emotesMutex_); + std::lock_guard lock(this->itemsMutex_); - // TODO: Implement more safely - auto it = this->emotes_.begin(); + auto it = this->items_.begin(); std::advance(it, index.row()); - return QVariant(it->str); + return QVariant(it->string); } int CompletionModel::rowCount(const QModelIndex &) const { - std::lock_guard lock(this->emotesMutex_); + std::lock_guard lock(this->itemsMutex_); - return this->emotes_.size(); + return this->items_.size(); } -void CompletionModel::refresh() +void CompletionModel::refresh(const QString &prefix) { - log("[CompletionModel:{}] Refreshing...]", this->channelName_); + std::lock_guard guard(this->itemsMutex_); + this->items_.clear(); - auto app = getApp(); + if (prefix.length() < 2) return; - // User-specific: Twitch Emotes - if (auto account = app->accounts->twitch.getCurrent()) { - for (const auto &emote : account->accessEmotes()->allEmoteNames) { - // XXX: No way to discern between a twitch global emote and sub - // emote right now - this->addString(emote.string, - TaggedString::Type::TwitchGlobalEmote); - } - } - - // // Global: BTTV Global Emotes - // std::vector &bttvGlobalEmoteCodes = - // app->emotes->bttv.globalEmoteNames_; for (const auto &m : - // bttvGlobalEmoteCodes) { - // this->addString(m, TaggedString::Type::BTTVGlobalEmote); - // } - - // // Global: FFZ Global Emotes - // std::vector &ffzGlobalEmoteCodes = - // app->emotes->ffz.globalEmoteCodes; for (const auto &m : - // ffzGlobalEmoteCodes) { - // this->addString(m, TaggedString::Type::FFZGlobalEmote); - // } - - // Channel emotes - if (auto channel = dynamic_cast( - getApp() - ->twitch2->getChannelOrEmptyByID(this->channelName_) - .get())) { - auto bttv = channel->bttvEmotes(); - // auto it = bttv->begin(); - // for (const auto &emote : *bttv) { - // } - // std::vector &bttvChannelEmoteCodes = - // app->emotes->bttv.channelEmoteName_[this->channelName_]; - // for (const auto &m : bttvChannelEmoteCodes) { - // this->addString(m, TaggedString::Type::BTTVChannelEmote); - // } - - // Channel-specific: FFZ Channel Emotes - for (const auto &emote : *channel->ffzEmotes()) { - this->addString(emote.second->name.string, - TaggedString::Type::FFZChannelEmote); - } - } - - // Emojis - const auto &emojiShortCodes = app->emotes->emojis.shortCodes; - for (const auto &m : emojiShortCodes) { - this->addString(":" + m + ":", TaggedString::Type::Emoji); - } - - // Commands - for (auto &command : app->commands->items.getVector()) { - this->addString(command.name, TaggedString::Command); - } - - for (auto &command : app->commands->getDefaultTwitchCommandList()) { - this->addString(command, TaggedString::Command); - } - - // Channel-specific: Usernames - // fourtf: only works with twitch chat - // auto c = - // ChannelManager::getInstance().getTwitchChannel(this->channelName); - // auto usernames = c->getUsernamesForCompletions(); - // for (const auto &name : usernames) { - // assert(!name.displayName.isEmpty()); - // this->addString(name.displayName); - // this->addString('@' + name.displayName); - - // if (!name.localizedName.isEmpty()) { - // this->addString(name.localizedName); - // this->addString('@' + name.localizedName); - // } - // } -} - -void CompletionModel::addString(const QString &str, TaggedString::Type type) -{ - std::lock_guard lock(this->emotesMutex_); - - // Always add a space at the end of completions - this->emotes_.insert({str + " ", type}); -} - -void CompletionModel::addUser(const QString &username) -{ - auto add = [this](const QString &str) { - auto ts = this->createUser(str + " "); - // Always add a space at the end of completions - std::pair::iterator, bool> p = - this->emotes_.insert(ts); - if (!p.second) { - // No inseration was made, figure out if we need to replace the - // username. - - if (p.first->str > ts.str) { - // Replace lowercase version of name with mixed-case version - this->emotes_.erase(p.first); - auto result2 = this->emotes_.insert(ts); - assert(result2.second); - } else { - p.first->timeAdded = std::chrono::steady_clock::now(); - } - } + auto addString = [&](const QString &str, TaggedString::Type type) { + if (str.startsWith(prefix, Qt::CaseInsensitive)) + this->items_.emplace(str + " ", type); }; - add(username); - add("@" + username); -} + if (auto channel = dynamic_cast(&this->channel_)) { + // account emotes + if (auto account = getApp()->accounts->twitch.getCurrent()) { + for (const auto &emote : account->accessEmotes()->allEmoteNames) { + // XXX: No way to discern between a twitch global emote and sub + // emote right now + addString(emote.string, TaggedString::Type::TwitchGlobalEmote); + } + } -void CompletionModel::clearExpiredStrings() -{ - std::lock_guard lock(this->emotesMutex_); + // Usernames + if (prefix.length() >= UsernameSet::PrefixLength) { + auto usernames = channel->accessChatters(); - auto now = std::chrono::steady_clock::now(); + for (const auto &name : usernames->subrange(Prefix(prefix))) { + addString(name, TaggedString::Type::Username); + addString("@" + name, TaggedString::Type::Username); + } + } - for (auto it = this->emotes_.begin(); it != this->emotes_.end();) { - const auto &taggedString = *it; + // Bttv Global + for (auto &emote : *channel->globalBttv().emotes()) { + addString(emote.first.string, TaggedString::Type::BTTVChannelEmote); + } - if (taggedString.isExpired(now)) { - // Log("String {} expired", taggedString.str); - it = this->emotes_.erase(it); - } else { - ++it; + // Ffz Global + for (auto &emote : *channel->globalFfz().emotes()) { + addString(emote.first.string, TaggedString::Type::FFZChannelEmote); + } + + // Bttv Channel + for (auto &emote : *channel->bttvEmotes()) { + addString(emote.first.string, TaggedString::Type::BTTVGlobalEmote); + } + + // Ffz Channel + for (auto &emote : *channel->ffzEmotes()) { + addString(emote.first.string, TaggedString::Type::BTTVGlobalEmote); + } + + // Emojis + if (prefix.startsWith(":")) { + const auto &emojiShortCodes = getApp()->emotes->emojis.shortCodes; + for (auto &m : emojiShortCodes) { + addString(":" + m + ":", TaggedString::Type::Emoji); + } + } + + // Commands + for (auto &command : getApp()->commands->items.getVector()) { + addString(command.name, TaggedString::Command); + } + + for (auto &command : + getApp()->commands->getDefaultTwitchCommandList()) { + addString(command, TaggedString::Command); } } } -CompletionModel::TaggedString CompletionModel::createUser(const QString &str) -{ - return TaggedString{str, TaggedString::Type::Username}; -} - } // namespace chatterino diff --git a/src/common/CompletionModel.hpp b/src/common/CompletionModel.hpp index d9baac7b3..f4f216cd1 100644 --- a/src/common/CompletionModel.hpp +++ b/src/common/CompletionModel.hpp @@ -1,7 +1,5 @@ #pragma once -#include "common/Common.hpp" - #include #include @@ -10,7 +8,7 @@ namespace chatterino { -class TwitchChannel; +class Channel; class CompletionModel : public QAbstractListModel { @@ -33,39 +31,30 @@ class CompletionModel : public QAbstractListModel Command, }; - TaggedString(const QString &_str, Type _type); + TaggedString(const QString &string, Type type); - bool isExpired(const std::chrono::steady_clock::time_point &now) const; bool isEmote() const; bool operator<(const TaggedString &that) const; - QString str; - // Type will help decide the lifetime of the tagged strings + QString string; Type type; - - mutable std::chrono::steady_clock::time_point timeAdded; }; public: - CompletionModel(const QString &channelName); + CompletionModel(Channel &channel); virtual int columnCount(const QModelIndex &) const override; virtual QVariant data(const QModelIndex &index, int) const override; virtual int rowCount(const QModelIndex &) const override; - void refresh(); - void addString(const QString &str, TaggedString::Type type); - void addUser(const QString &str); - - void clearExpiredStrings(); + void refresh(const QString &prefix); private: TaggedString createUser(const QString &str); - mutable std::mutex emotesMutex_; - std::set emotes_; - - QString channelName_; + std::set items_; + mutable std::mutex itemsMutex_; + Channel &channel_; }; } // namespace chatterino diff --git a/src/common/NetworkCommon.hpp b/src/common/NetworkCommon.hpp index 30d1e9267..b214f5472 100644 --- a/src/common/NetworkCommon.hpp +++ b/src/common/NetworkCommon.hpp @@ -2,12 +2,11 @@ #include -#include "Common.hpp" - class QNetworkReply; namespace chatterino { +class Outcome; class NetworkResult; using NetworkSuccessCallback = std::function; diff --git a/src/common/NetworkData.cpp b/src/common/NetworkData.cpp index ad8fe40b2..d3434576e 100644 --- a/src/common/NetworkData.cpp +++ b/src/common/NetworkData.cpp @@ -44,7 +44,7 @@ void NetworkData::writeToCache(const QByteArray &bytes) if (this->useQuickLoadCache_) { auto app = getApp(); - QFile cachedFile(app->paths->cacheDirectory + "/" + this->getHash()); + QFile cachedFile(getPaths()->cacheDirectory + "/" + this->getHash()); if (cachedFile.open(QIODevice::WriteOnly)) { cachedFile.write(bytes); diff --git a/src/common/NetworkManager.hpp b/src/common/NetworkManager.hpp index 12c0c6a51..530aaae1f 100644 --- a/src/common/NetworkManager.hpp +++ b/src/common/NetworkManager.hpp @@ -1,16 +1,7 @@ #pragma once -#include "common/NetworkRequester.hpp" -#include "common/NetworkWorker.hpp" -#include "debug/Log.hpp" - -#include -#include #include -#include #include -#include -#include namespace chatterino { diff --git a/src/common/NetworkRequest.cpp b/src/common/NetworkRequest.cpp index f54192190..af27394de 100644 --- a/src/common/NetworkRequest.cpp +++ b/src/common/NetworkRequest.cpp @@ -1,7 +1,9 @@ #include "common/NetworkRequest.hpp" #include "Application.hpp" +#include "common/NetworkData.hpp" #include "common/NetworkManager.hpp" +#include "common/Outcome.hpp" #include "debug/Log.hpp" #include "providers/twitch/TwitchCommon.hpp" #include "singletons/Paths.hpp" @@ -145,7 +147,7 @@ Outcome NetworkRequest::tryLoadCachedFile() { auto app = getApp(); - QFile cachedFile(app->paths->cacheDirectory + "/" + this->data->getHash()); + QFile cachedFile(getPaths()->cacheDirectory + "/" + this->data->getHash()); if (!cachedFile.exists()) { // File didn't exist diff --git a/src/common/NetworkRequest.hpp b/src/common/NetworkRequest.hpp index 09b2cccee..f1940621d 100644 --- a/src/common/NetworkRequest.hpp +++ b/src/common/NetworkRequest.hpp @@ -1,14 +1,13 @@ #pragma once -#include "Application.hpp" #include "common/NetworkCommon.hpp" -#include "common/NetworkData.hpp" #include "common/NetworkRequester.hpp" #include "common/NetworkResult.hpp" #include "common/NetworkTimer.hpp" #include "common/NetworkWorker.hpp" namespace chatterino { +class NetworkData; class NetworkRequest { diff --git a/src/common/UsernameSet.cpp b/src/common/UsernameSet.cpp new file mode 100644 index 000000000..064b6cf54 --- /dev/null +++ b/src/common/UsernameSet.cpp @@ -0,0 +1,112 @@ +#include "UsernameSet.hpp" + +#include + +namespace chatterino { + +// +// UsernameSet +// + +UsernameSet::ConstIterator UsernameSet::begin() const +{ + return this->items.begin(); +} + +UsernameSet::ConstIterator UsernameSet::end() const +{ + return this->items.end(); +} + +UsernameSet::Range UsernameSet::subrange(const Prefix &prefix) const +{ + auto it = this->firstKeyForPrefix.find(prefix); + if (it != this->firstKeyForPrefix.end()) { + auto start = this->items.find(it->second); + auto end = start; + + while (end != this->items.end() && prefix.isStartOf(*end)) { + end++; + } + return {start, end}; + } + + return {this->items.end(), this->items.end()}; +} + +std::set::size_type UsernameSet::size() const +{ + return this->items.size(); +} + +std::pair UsernameSet::insert(const QString &value) +{ + this->insertPrefix(value); + + return this->items.insert(value); +} + +std::pair UsernameSet::insert(QString &&value) +{ + this->insertPrefix(value); + + return this->items.insert(std::move(value)); +} + +void UsernameSet::insertPrefix(const QString &value) +{ + auto &string = this->firstKeyForPrefix[Prefix(value)]; + + if (string.isNull() || value < string) string = value; +} + +// +// Range +// + +UsernameSet::Range::Range(ConstIterator start, ConstIterator end) + : start_(start) + , end_(end) +{ +} + +UsernameSet::ConstIterator UsernameSet::Range::begin() +{ + return this->start_; +} + +UsernameSet::ConstIterator UsernameSet::Range::end() +{ + return this->end_; +} + +// +// Prefix +// + +Prefix::Prefix(const QString &string) + : first(string.size() >= 1 ? string[0].toLower() : '\0') + , second(string.size() >= 2 ? string[1].toLower() : '\0') +{ +} + +bool Prefix::operator==(const Prefix &other) const +{ + return std::tie(this->first, this->second) == + std::tie(other.first, other.second); +} + +bool Prefix::isStartOf(const QString &string) const +{ + if (string.size() == 0) { + return this->first == QChar('\0') && this->second == QChar('\0'); + } else if (string.size() == 1) { + return this->first == string[0].toLower() && + this->second == QChar('\0'); + } else { + return this->first == string[0].toLower() && + this->second == string[1].toLower(); + } +} + +} // namespace chatterino diff --git a/src/common/UsernameSet.hpp b/src/common/UsernameSet.hpp new file mode 100644 index 000000000..d71bcdaf6 --- /dev/null +++ b/src/common/UsernameSet.hpp @@ -0,0 +1,73 @@ +#pragma once + +#include +#include +#include +#include + +namespace chatterino { +class Prefix +{ +public: + Prefix(const QString &string); + bool operator==(const Prefix &other) const; + bool isStartOf(const QString &string) const; + +private: + QChar first; + QChar second; + + friend struct std::hash; +}; +} // namespace chatterino + +namespace std { +template <> +struct hash { + size_t operator()(const chatterino::Prefix &prefix) const + { + return (size_t(prefix.first.unicode()) << 16) | + size_t(prefix.second.unicode()); + } +}; +} // namespace std + +namespace chatterino { +class UsernameSet +{ +public: + static constexpr int PrefixLength = 2; + + using Iterator = std::set::iterator; + using ConstIterator = std::set::const_iterator; + + class Range + { + public: + Range(ConstIterator start, ConstIterator end); + + ConstIterator begin(); + ConstIterator end(); + + private: + ConstIterator start_; + ConstIterator end_; + }; + + ConstIterator begin() const; + ConstIterator end() const; + Range subrange(const Prefix &prefix) const; + + std::set::size_type size() const; + + std::pair insert(const QString &value); + std::pair insert(QString &&value); + +private: + void insertPrefix(const QString &string); + + std::set items; + std::unordered_map firstKeyForPrefix; +}; + +} // namespace chatterino diff --git a/src/common/Version.hpp b/src/common/Version.hpp index 2b1b51b39..4101f3a56 100644 --- a/src/common/Version.hpp +++ b/src/common/Version.hpp @@ -5,9 +5,9 @@ #define CHATTERINO_VERSION "2.0.4" #if defined(Q_OS_WIN) -#define CHATTERINO_OS "win" +# define CHATTERINO_OS "win" #elif defined(Q_OS_MACOS) -#define CHATTERINO_OS "macos" +# define CHATTERINO_OS "macos" #elif defined(Q_OS_LINUX) -#define CHATTERINO_OS "linux" +# define CHATTERINO_OS "linux" #endif diff --git a/src/controllers/accounts/AccountController.cpp b/src/controllers/accounts/AccountController.cpp index 6cd1f2558..7d2c84266 100644 --- a/src/controllers/accounts/AccountController.cpp +++ b/src/controllers/accounts/AccountController.cpp @@ -1,6 +1,8 @@ #include "AccountController.hpp" +#include "controllers/accounts/Account.hpp" #include "controllers/accounts/AccountModel.hpp" +#include "providers/twitch/TwitchAccount.hpp" namespace chatterino { diff --git a/src/controllers/accounts/AccountController.hpp b/src/controllers/accounts/AccountController.hpp index 023ad9c9e..b47ec86be 100644 --- a/src/controllers/accounts/AccountController.hpp +++ b/src/controllers/accounts/AccountController.hpp @@ -1,16 +1,15 @@ #pragma once -#include "common/Singleton.hpp" - #include #include "common/SignalVector.hpp" -#include "controllers/accounts/Account.hpp" +#include "common/Singleton.hpp" #include "providers/twitch/TwitchAccountManager.hpp" #include "util/SharedPtrElementLess.hpp" namespace chatterino { +class Account; class Settings; class Paths; diff --git a/src/controllers/accounts/AccountModel.cpp b/src/controllers/accounts/AccountModel.cpp index d63f2c288..23ded2881 100644 --- a/src/controllers/accounts/AccountModel.cpp +++ b/src/controllers/accounts/AccountModel.cpp @@ -1,5 +1,6 @@ #include "AccountModel.hpp" +#include "controllers/accounts/Account.hpp" #include "util/StandardItemHelper.hpp" namespace chatterino { diff --git a/src/controllers/accounts/AccountModel.hpp b/src/controllers/accounts/AccountModel.hpp index c0b4538d5..a8b020410 100644 --- a/src/controllers/accounts/AccountModel.hpp +++ b/src/controllers/accounts/AccountModel.hpp @@ -8,6 +8,7 @@ namespace chatterino { +class Account; class AccountController; class AccountModel : public SignalVectorModel> diff --git a/src/controllers/commands/CommandController.cpp b/src/controllers/commands/CommandController.cpp index 3aaefca8b..f931aa073 100644 --- a/src/controllers/commands/CommandController.cpp +++ b/src/controllers/commands/CommandController.cpp @@ -5,7 +5,10 @@ #include "controllers/accounts/AccountController.hpp" #include "controllers/commands/Command.hpp" #include "controllers/commands/CommandModel.hpp" +#include "debug/Log.hpp" +#include "messages/Message.hpp" #include "messages/MessageBuilder.hpp" +#include "messages/MessageElement.hpp" #include "providers/twitch/TwitchApi.hpp" #include "providers/twitch/TwitchChannel.hpp" #include "providers/twitch/TwitchServer.hpp" diff --git a/src/controllers/highlights/HighlightBlacklistUser.hpp b/src/controllers/highlights/HighlightBlacklistUser.hpp index 4ce8683d0..b2e3e4a06 100644 --- a/src/controllers/highlights/HighlightBlacklistUser.hpp +++ b/src/controllers/highlights/HighlightBlacklistUser.hpp @@ -68,37 +68,39 @@ private: namespace pajlada { namespace Settings { -template <> -struct Serialize { - static rapidjson::Value get(const chatterino::HighlightBlacklistUser &value, - rapidjson::Document::AllocatorType &a) - { - rapidjson::Value ret(rapidjson::kObjectType); + template <> + struct Serialize { + static rapidjson::Value get( + const chatterino::HighlightBlacklistUser &value, + rapidjson::Document::AllocatorType &a) + { + rapidjson::Value ret(rapidjson::kObjectType); - AddMember(ret, "pattern", value.getPattern(), a); - AddMember(ret, "regex", value.isRegex(), a); + AddMember(ret, "pattern", value.getPattern(), a); + AddMember(ret, "regex", value.isRegex(), a); - return ret; - } -}; + return ret; + } + }; -template <> -struct Deserialize { - static chatterino::HighlightBlacklistUser get(const rapidjson::Value &value) - { - QString pattern; - bool isRegex = false; + template <> + struct Deserialize { + static chatterino::HighlightBlacklistUser get( + const rapidjson::Value &value) + { + QString pattern; + bool isRegex = false; + + if (!value.IsObject()) { + return chatterino::HighlightBlacklistUser(pattern, isRegex); + } + + chatterino::rj::getSafe(value, "pattern", pattern); + chatterino::rj::getSafe(value, "regex", isRegex); - if (!value.IsObject()) { return chatterino::HighlightBlacklistUser(pattern, isRegex); } - - chatterino::rj::getSafe(value, "pattern", pattern); - chatterino::rj::getSafe(value, "regex", isRegex); - - return chatterino::HighlightBlacklistUser(pattern, isRegex); - } -}; + }; } // namespace Settings } // namespace pajlada diff --git a/src/controllers/highlights/HighlightController.hpp b/src/controllers/highlights/HighlightController.hpp index 09d686cb2..963f23aaa 100644 --- a/src/controllers/highlights/HighlightController.hpp +++ b/src/controllers/highlights/HighlightController.hpp @@ -1,15 +1,16 @@ #pragma once -#include "common/Singleton.hpp" - +#include "common/ChatterinoSetting.hpp" #include "common/SignalVector.hpp" +#include "common/Singleton.hpp" #include "controllers/highlights/HighlightBlacklistUser.hpp" #include "controllers/highlights/HighlightPhrase.hpp" -#include "messages/Message.hpp" -#include "singletons/Settings.hpp" namespace chatterino { +struct Message; +using MessagePtr = std::shared_ptr; + class Settings; class Paths; diff --git a/src/controllers/highlights/HighlightModel.cpp b/src/controllers/highlights/HighlightModel.cpp index cad2fd006..827e23a4a 100644 --- a/src/controllers/highlights/HighlightModel.cpp +++ b/src/controllers/highlights/HighlightModel.cpp @@ -37,13 +37,13 @@ void HighlightModel::getRowFromItem(const HighlightPhrase &item, void HighlightModel::afterInit() { std::vector row = this->createRow(); - setBoolItem(row[0], getApp()->settings->enableHighlightsSelf.getValue(), - true, false); + setBoolItem(row[0], getSettings()->enableHighlightsSelf.getValue(), true, + false); row[0]->setData("Your username (automatic)", Qt::DisplayRole); - setBoolItem(row[1], getApp()->settings->enableHighlightTaskbar.getValue(), - true, false); - setBoolItem(row[2], getApp()->settings->enableHighlightSound.getValue(), - true, false); + setBoolItem(row[1], getSettings()->enableHighlightTaskbar.getValue(), true, + false); + setBoolItem(row[2], getSettings()->enableHighlightSound.getValue(), true, + false); row[3]->setFlags(0); this->insertCustomRow(row, 0); } @@ -55,20 +55,17 @@ void HighlightModel::customRowSetData(const std::vector &row, switch (column) { case 0: { if (role == Qt::CheckStateRole) { - getApp()->settings->enableHighlightsSelf.setValue( - value.toBool()); + getSettings()->enableHighlightsSelf.setValue(value.toBool()); } } break; case 1: { if (role == Qt::CheckStateRole) { - getApp()->settings->enableHighlightTaskbar.setValue( - value.toBool()); + getSettings()->enableHighlightTaskbar.setValue(value.toBool()); } } break; case 2: { if (role == Qt::CheckStateRole) { - getApp()->settings->enableHighlightSound.setValue( - value.toBool()); + getSettings()->enableHighlightSound.setValue(value.toBool()); } } break; case 3: { diff --git a/src/controllers/highlights/HighlightPhrase.hpp b/src/controllers/highlights/HighlightPhrase.hpp index 91abaadce..cfbee6588 100644 --- a/src/controllers/highlights/HighlightPhrase.hpp +++ b/src/controllers/highlights/HighlightPhrase.hpp @@ -72,43 +72,45 @@ private: namespace pajlada { namespace Settings { -template <> -struct Serialize { - static rapidjson::Value get(const chatterino::HighlightPhrase &value, - rapidjson::Document::AllocatorType &a) - { - rapidjson::Value ret(rapidjson::kObjectType); + template <> + struct Serialize { + static rapidjson::Value get(const chatterino::HighlightPhrase &value, + rapidjson::Document::AllocatorType &a) + { + rapidjson::Value ret(rapidjson::kObjectType); - AddMember(ret, "pattern", value.getPattern(), a); - AddMember(ret, "alert", value.getAlert(), a); - AddMember(ret, "sound", value.getSound(), a); - AddMember(ret, "regex", value.isRegex(), a); + AddMember(ret, "pattern", value.getPattern(), a); + AddMember(ret, "alert", value.getAlert(), a); + AddMember(ret, "sound", value.getSound(), a); + AddMember(ret, "regex", value.isRegex(), a); - return ret; - } -}; - -template <> -struct Deserialize { - static chatterino::HighlightPhrase get(const rapidjson::Value &value) - { - if (!value.IsObject()) { - return chatterino::HighlightPhrase(QString(), true, false, false); + return ret; } + }; - QString _pattern; - bool _alert = true; - bool _sound = false; - bool _isRegex = false; + template <> + struct Deserialize { + static chatterino::HighlightPhrase get(const rapidjson::Value &value) + { + if (!value.IsObject()) { + return chatterino::HighlightPhrase(QString(), true, false, + false); + } - chatterino::rj::getSafe(value, "pattern", _pattern); - chatterino::rj::getSafe(value, "alert", _alert); - chatterino::rj::getSafe(value, "sound", _sound); - chatterino::rj::getSafe(value, "regex", _isRegex); + QString _pattern; + bool _alert = true; + bool _sound = false; + bool _isRegex = false; - return chatterino::HighlightPhrase(_pattern, _alert, _sound, _isRegex); - } -}; + chatterino::rj::getSafe(value, "pattern", _pattern); + chatterino::rj::getSafe(value, "alert", _alert); + chatterino::rj::getSafe(value, "sound", _sound); + chatterino::rj::getSafe(value, "regex", _isRegex); + + return chatterino::HighlightPhrase(_pattern, _alert, _sound, + _isRegex); + } + }; } // namespace Settings } // namespace pajlada diff --git a/src/controllers/ignores/IgnoreController.hpp b/src/controllers/ignores/IgnoreController.hpp index e7a0472e6..10c83ac10 100644 --- a/src/controllers/ignores/IgnoreController.hpp +++ b/src/controllers/ignores/IgnoreController.hpp @@ -1,10 +1,9 @@ #pragma once -#include "common/Singleton.hpp" - +#include "common/ChatterinoSetting.hpp" #include "common/SignalVector.hpp" +#include "common/Singleton.hpp" #include "controllers/ignores/IgnorePhrase.hpp" -#include "singletons/Settings.hpp" namespace chatterino { diff --git a/src/controllers/ignores/IgnorePhrase.hpp b/src/controllers/ignores/IgnorePhrase.hpp index c53ecfd51..e60e90827 100644 --- a/src/controllers/ignores/IgnorePhrase.hpp +++ b/src/controllers/ignores/IgnorePhrase.hpp @@ -150,22 +150,23 @@ struct Deserialize { QString(), false, false, ::chatterino::getSettings()->ignoredPhraseReplace.getValue(), true); } + }; - QString _pattern; - bool _isRegex = false; - bool _isBlock = false; - QString _replace; - bool _caseSens = true; + QString _pattern; + bool _isRegex = false; + bool _isBlock = false; + QString _replace; + bool _caseSens = true; - chatterino::rj::getSafe(value, "pattern", _pattern); - chatterino::rj::getSafe(value, "regex", _isRegex); - chatterino::rj::getSafe(value, "isBlock", _isBlock); - chatterino::rj::getSafe(value, "replaceWith", _replace); - chatterino::rj::getSafe(value, "caseSensitive", _caseSens); + chatterino::rj::getSafe(value, "pattern", _pattern); + chatterino::rj::getSafe(value, "regex", _isRegex); + chatterino::rj::getSafe(value, "isBlock", _isBlock); + chatterino::rj::getSafe(value, "replaceWith", _replace); + chatterino::rj::getSafe(value, "caseSensitive", _caseSens); - return chatterino::IgnorePhrase(_pattern, _isRegex, _isBlock, _replace, _caseSens); - } -}; + return chatterino::IgnorePhrase(_pattern, _isRegex, _isBlock, _replace, _caseSens); +} +}; // namespace Settings -} // namespace Settings +} // namespace pajlada } // namespace pajlada diff --git a/src/controllers/moderationactions/ModerationAction.cpp b/src/controllers/moderationactions/ModerationAction.cpp index 49d302b34..cc2b8463e 100644 --- a/src/controllers/moderationactions/ModerationAction.cpp +++ b/src/controllers/moderationactions/ModerationAction.cpp @@ -2,6 +2,7 @@ #include #include "Application.hpp" +#include "messages/Image.hpp" #include "singletons/Resources.hpp" namespace chatterino { diff --git a/src/controllers/moderationactions/ModerationAction.hpp b/src/controllers/moderationactions/ModerationAction.hpp index f509f79c5..8dbdba93f 100644 --- a/src/controllers/moderationactions/ModerationAction.hpp +++ b/src/controllers/moderationactions/ModerationAction.hpp @@ -4,11 +4,13 @@ #include #include -#include "messages/Image.hpp" #include "util/RapidjsonHelpers.hpp" namespace chatterino { +class Image; +using ImagePtr = std::shared_ptr; + class ModerationAction { public: @@ -34,34 +36,34 @@ private: namespace pajlada { namespace Settings { -template <> -struct Serialize { - static rapidjson::Value get(const chatterino::ModerationAction &value, - rapidjson::Document::AllocatorType &a) - { - rapidjson::Value ret(rapidjson::kObjectType); + template <> + struct Serialize { + static rapidjson::Value get(const chatterino::ModerationAction &value, + rapidjson::Document::AllocatorType &a) + { + rapidjson::Value ret(rapidjson::kObjectType); - AddMember(ret, "pattern", value.getAction(), a); + AddMember(ret, "pattern", value.getAction(), a); - return ret; - } -}; - -template <> -struct Deserialize { - static chatterino::ModerationAction get(const rapidjson::Value &value) - { - if (!value.IsObject()) { - return chatterino::ModerationAction(QString()); + return ret; } + }; - QString pattern; + template <> + struct Deserialize { + static chatterino::ModerationAction get(const rapidjson::Value &value) + { + if (!value.IsObject()) { + return chatterino::ModerationAction(QString()); + } - chatterino::rj::getSafe(value, "pattern", pattern); + QString pattern; - return chatterino::ModerationAction(pattern); - } -}; + chatterino::rj::getSafe(value, "pattern", pattern); + + return chatterino::ModerationAction(pattern); + } + }; } // namespace Settings } // namespace pajlada diff --git a/src/controllers/moderationactions/ModerationActions.cpp b/src/controllers/moderationactions/ModerationActions.cpp index 68070f6b2..27cd5de30 100644 --- a/src/controllers/moderationactions/ModerationActions.cpp +++ b/src/controllers/moderationactions/ModerationActions.cpp @@ -17,12 +17,16 @@ void ModerationActions::initialize(Settings &settings, Paths &paths) assert(!this->initialized_); this->initialized_ = true; - for (auto &val : this->setting_.getValue()) { + this->setting_ = + std::make_unique>>( + "/moderation/actions"); + + for (auto &val : this->setting_->getValue()) { this->items.insertItem(val); } this->items.delayedItemsChanged.connect([this] { // - this->setting_.setValue(this->items.getVector()); + this->setting_->setValue(this->items.getVector()); }); } diff --git a/src/controllers/moderationactions/ModerationActions.hpp b/src/controllers/moderationactions/ModerationActions.hpp index d5e510f1c..08314eb22 100644 --- a/src/controllers/moderationactions/ModerationActions.hpp +++ b/src/controllers/moderationactions/ModerationActions.hpp @@ -25,8 +25,7 @@ public: ModerationActionModel *createModel(QObject *parent); private: - ChatterinoSetting> setting_ = { - "/moderation/actions"}; + std::unique_ptr>> setting_; bool initialized_ = false; }; diff --git a/src/controllers/taggedusers/TaggedUsersModel.cpp b/src/controllers/taggedusers/TaggedUsersModel.cpp index e3225888c..037b4ae63 100644 --- a/src/controllers/taggedusers/TaggedUsersModel.cpp +++ b/src/controllers/taggedusers/TaggedUsersModel.cpp @@ -29,12 +29,12 @@ void TaggedUsersModel::afterInit() { // std::vector row = this->createRow(); // setBoolItem(row[0], - // getApp()->settings->enableHighlightsSelf.getValue(), true, false); + // getSettings()->enableHighlightsSelf.getValue(), true, false); // row[0]->setData("Your username (automatic)", Qt::DisplayRole); // setBoolItem(row[1], - // getApp()->settings->enableHighlightTaskbar.getValue(), true, false); + // getSettings()->enableHighlightTaskbar.getValue(), true, false); // setBoolItem(row[2], - // getApp()->settings->enableHighlightSound.getValue(), true, false); + // getSettings()->enableHighlightSound.getValue(), true, false); // row[3]->setFlags(0); this->insertCustomRow(row, 0); } @@ -45,17 +45,17 @@ void TaggedUsersModel::afterInit() // switch (column) { // case 0: { // if (role == Qt::CheckStateRole) { -// getApp()->settings->enableHighlightsSelf.setValue(value.toBool()); +// getSettings()->enableHighlightsSelf.setValue(value.toBool()); // } // } break; // case 1: { // if (role == Qt::CheckStateRole) { -// getApp()->settings->enableHighlightTaskbar.setValue(value.toBool()); +// getSettings()->enableHighlightTaskbar.setValue(value.toBool()); // } // } break; // case 2: { // if (role == Qt::CheckStateRole) { -// getApp()->settings->enableHighlightSound.setValue(value.toBool()); +// getSettings()->enableHighlightSound.setValue(value.toBool()); // } // } break; // case 3: { diff --git a/src/messages/Emote.hpp b/src/messages/Emote.hpp index 89cadc623..143d3ea64 100644 --- a/src/messages/Emote.hpp +++ b/src/messages/Emote.hpp @@ -7,9 +7,6 @@ #include #include -QStringAlias(EmoteId); -QStringAlias(EmoteName); - namespace chatterino { struct Emote { diff --git a/src/messages/EmoteCache.hpp b/src/messages/EmoteCache.hpp deleted file mode 100644 index 1bbb01fd4..000000000 --- a/src/messages/EmoteCache.hpp +++ /dev/null @@ -1,93 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -#include "common/UniqueAccess.hpp" -#include "messages/Emote.hpp" - -namespace chatterino { - -template -class MapReplacement -{ -public: - MapReplacement(std::unordered_map &items) - : oldItems_(items) - { - } - - void add(const TKey &key, const Emote &data) - { - this->add(key, Emote(data)); - } - - void add(const TKey &key, Emote &&data) - { - auto it = this->oldItems_.find(key); - if (it != this->oldItems_.end() && *it->second == data) { - this->newItems_[key] = it->second; - } else { - this->newItems_[key] = std::make_shared(std::move(data)); - } - } - - void apply() - { - this->oldItems_ = std::move(this->newItems_); - } - -private: - std::unordered_map &oldItems_; - std::unordered_map newItems_; -}; - -template -class EmoteCache -{ -public: - using Iterator = typename std::unordered_map::iterator; - using ConstIterator = typename std::unordered_map::iterator; - - Iterator begin() - { - return this->items_.begin(); - } - - ConstIterator begin() const - { - return this->items_.begin(); - } - - Iterator end() - { - return this->items_.end(); - } - - ConstIterator end() const - { - return this->items_.end(); - } - - boost::optional get(const TKey &key) const - { - auto it = this->items_.find(key); - - if (it == this->items_.end()) - return boost::none; - else - return it->second; - } - - MapReplacement makeReplacment() - { - return MapReplacement(this->items_); - } - -private: - std::unordered_map items_; -}; - -} // namespace chatterino diff --git a/src/messages/Image.cpp b/src/messages/Image.cpp index 572432cdd..e6dcfbc8b 100644 --- a/src/messages/Image.cpp +++ b/src/messages/Image.cpp @@ -1,6 +1,7 @@ #include "messages/Image.hpp" #include "Application.hpp" +#include "common/Common.hpp" #include "common/NetworkRequest.hpp" #include "debug/AssertInGuiThread.hpp" #include "debug/Benchmark.hpp" @@ -21,157 +22,163 @@ namespace chatterino { namespace { -// Frames -Frames::Frames() -{ - DebugCount::increase("images"); -} - -Frames::Frames(const QVector> &frames) - : items_(frames) -{ - assertInGuiThread(); - DebugCount::increase("images"); - - if (this->animated()) { - DebugCount::increase("animated images"); - - this->gifTimerConnection_ = getApp()->emotes->gifTimer.signal.connect( - [this] { this->advance(); }); - } -} - -Frames::~Frames() -{ - assertInGuiThread(); - DebugCount::decrease("images"); - - if (this->animated()) { - DebugCount::decrease("animated images"); + // Frames + Frames::Frames() + { + DebugCount::increase("images"); } - this->gifTimerConnection_.disconnect(); -} + Frames::Frames(const QVector> &frames) + : items_(frames) + { + assertInGuiThread(); + DebugCount::increase("images"); -void Frames::advance() -{ - this->durationOffset_ += GIF_FRAME_LENGTH; + if (this->animated()) { + DebugCount::increase("animated images"); - while (true) { - this->index_ %= this->items_.size(); - - if (this->index_ >= this->items_.size()) { - this->index_ = this->index_; - } - - if (this->durationOffset_ > this->items_[this->index_].duration) { - this->durationOffset_ -= this->items_[this->index_].duration; - this->index_ = (this->index_ + 1) % this->items_.size(); - } else { - break; + this->gifTimerConnection_ = + getApp()->emotes->gifTimer.signal.connect( + [this] { this->advance(); }); } } -} -bool Frames::animated() const -{ - return this->items_.size() > 1; -} + Frames::~Frames() + { + assertInGuiThread(); + DebugCount::decrease("images"); -boost::optional Frames::current() const -{ - if (this->items_.size() == 0) return boost::none; - return this->items_[this->index_].image; -} + if (this->animated()) { + DebugCount::decrease("animated images"); + } -boost::optional Frames::first() const -{ - if (this->items_.size() == 0) return boost::none; - return this->items_.front().image; -} + this->gifTimerConnection_.disconnect(); + } -// functions -QVector> readFrames(QImageReader &reader, const Url &url) -{ - QVector> frames; + void Frames::advance() + { + this->durationOffset_ += GIF_FRAME_LENGTH; + + while (true) { + this->index_ %= this->items_.size(); + + if (this->index_ >= this->items_.size()) { + this->index_ = this->index_; + } + + if (this->durationOffset_ > this->items_[this->index_].duration) { + this->durationOffset_ -= this->items_[this->index_].duration; + this->index_ = (this->index_ + 1) % this->items_.size(); + } else { + break; + } + } + } + + bool Frames::animated() const + { + return this->items_.size() > 1; + } + + boost::optional Frames::current() const + { + if (this->items_.size() == 0) return boost::none; + return this->items_[this->index_].image; + } + + boost::optional Frames::first() const + { + if (this->items_.size() == 0) return boost::none; + return this->items_.front().image; + } + + // functions + QVector> readFrames(QImageReader &reader, const Url &url) + { + QVector> frames; + + if (reader.imageCount() == 0) { + log("Error while reading image {}: '{}'", url.string, + reader.errorString()); + return frames; + } + + QImage image; + for (int index = 0; index < reader.imageCount(); ++index) { + if (reader.read(&image)) { + QPixmap::fromImage(image); + + int duration = std::max(20, reader.nextImageDelay()); + frames.push_back(Frame{image, duration}); + } + } + + if (frames.size() == 0) { + log("Error while reading image {}: '{}'", url.string, + reader.errorString()); + } - if (reader.imageCount() == 0) { - log("Error while reading image {}: '{}'", url.string, - reader.errorString()); return frames; } - QImage image; - for (int index = 0; index < reader.imageCount(); ++index) { - if (reader.read(&image)) { - QPixmap::fromImage(image); - - int duration = std::max(20, reader.nextImageDelay()); - frames.push_back(Frame{image, duration}); - } - } - - if (frames.size() == 0) { - log("Error while reading image {}: '{}'", url.string, - reader.errorString()); - } - - return frames; -} - -// parsed -template -void assignDelayed( - std::queue>>> &queued, - std::mutex &mutex, std::atomic_bool &loadedEventQueued) -{ - std::lock_guard lock(mutex); - int i = 0; - - while (!queued.empty()) { - queued.front().first(queued.front().second); - queued.pop(); - - if (++i > 50) { - QTimer::singleShot( - 3, [&] { assignDelayed(queued, mutex, loadedEventQueued); }); - return; - } - } - - getApp()->windows->forceLayoutChannelViews(); - loadedEventQueued = false; -} - -template -auto makeConvertCallback(const QVector> &parsed, Assign assign) -{ - return [parsed, assign] { - // convert to pixmap - auto frames = QVector>(); - std::transform(parsed.begin(), parsed.end(), std::back_inserter(frames), - [](auto &frame) { - return Frame{ - QPixmap::fromImage(frame.image), frame.duration}; - }); - - // put into stack - static std::queue>>> queued; - static std::mutex mutex; - + // parsed + template + void assignDelayed( + std::queue>>> &queued, + std::mutex &mutex, std::atomic_bool &loadedEventQueued) + { std::lock_guard lock(mutex); - queued.emplace(assign, frames); + int i = 0; - static std::atomic_bool loadedEventQueued{false}; + while (!queued.empty()) { + queued.front().first(queued.front().second); + queued.pop(); - if (!loadedEventQueued) { - loadedEventQueued = true; - - QTimer::singleShot( - 100, [=] { assignDelayed(queued, mutex, loadedEventQueued); }); + if (++i > 50) { + QTimer::singleShot(3, [&] { + assignDelayed(queued, mutex, loadedEventQueued); + }); + return; + } } - }; -} + + getApp()->windows->forceLayoutChannelViews(); + loadedEventQueued = false; + } + + template + auto makeConvertCallback(const QVector> &parsed, + Assign assign) + { + return [parsed, assign] { + // convert to pixmap + auto frames = QVector>(); + std::transform(parsed.begin(), parsed.end(), + std::back_inserter(frames), [](auto &frame) { + return Frame{ + QPixmap::fromImage(frame.image), + frame.duration}; + }); + + // put into stack + static std::queue>>> + queued; + static std::mutex mutex; + + std::lock_guard lock(mutex); + queued.emplace(assign, frames); + + static std::atomic_bool loadedEventQueued{false}; + + if (!loadedEventQueued) { + loadedEventQueued = true; + + QTimer::singleShot(100, [=] { + assignDelayed(queued, mutex, loadedEventQueued); + }); + } + }; + } } // namespace // IMAGE2 diff --git a/src/messages/Image.hpp b/src/messages/Image.hpp index 3d869f798..678af25ce 100644 --- a/src/messages/Image.hpp +++ b/src/messages/Image.hpp @@ -1,45 +1,45 @@ #pragma once -#include "common/Common.hpp" - #include #include #include #include #include #include +#include #include #include #include #include +#include "common/Aliases.hpp" #include "common/NullablePtr.hpp" namespace chatterino { namespace { -template -struct Frame { - Image image; - int duration; -}; -class Frames : boost::noncopyable -{ -public: - Frames(); - Frames(const QVector> &frames); - ~Frames(); + template + struct Frame { + Image image; + int duration; + }; + class Frames : boost::noncopyable + { + public: + Frames(); + Frames(const QVector> &frames); + ~Frames(); - bool animated() const; - void advance(); - boost::optional current() const; - boost::optional first() const; + bool animated() const; + void advance(); + boost::optional current() const; + boost::optional first() const; -private: - QVector> items_; - int index_{0}; - int durationOffset_{0}; - pajlada::Signals::Connection gifTimerConnection_; -}; + private: + QVector> items_; + int index_{0}; + int durationOffset_{0}; + pajlada::Signals::Connection gifTimerConnection_; + }; } // namespace class Image; diff --git a/src/messages/ImageSet.cpp b/src/messages/ImageSet.cpp index 0e500816f..1019d27e9 100644 --- a/src/messages/ImageSet.cpp +++ b/src/messages/ImageSet.cpp @@ -76,7 +76,7 @@ const ImagePtr &ImageSet::getImage(float scale) const } if (!this->imageX2_->isEmpty() && quality == 2) { - return this->imageX3_; + return this->imageX2_; } return this->imageX1_; diff --git a/src/messages/Link.hpp b/src/messages/Link.hpp index a6c503540..a07d21cf3 100644 --- a/src/messages/Link.hpp +++ b/src/messages/Link.hpp @@ -1,7 +1,6 @@ #pragma once #include -#include namespace chatterino { diff --git a/src/messages/Message.hpp b/src/messages/Message.hpp index 76039ec41..7aacc47e5 100644 --- a/src/messages/Message.hpp +++ b/src/messages/Message.hpp @@ -1,16 +1,16 @@ #pragma once #include "common/FlagsEnum.hpp" -#include "messages/MessageElement.hpp" -#include "providers/twitch/PubsubActions.hpp" #include "widgets/helper/ScrollbarHighlight.hpp" #include +#include #include #include #include namespace chatterino { +class MessageElement; enum class MessageFlag : uint16_t { None = 0, diff --git a/src/messages/MessageBuilder.cpp b/src/messages/MessageBuilder.cpp index f4ebd2301..49c96c946 100644 --- a/src/messages/MessageBuilder.cpp +++ b/src/messages/MessageBuilder.cpp @@ -1,6 +1,9 @@ #include "MessageBuilder.hpp" #include "common/LinkParser.hpp" +#include "messages/Message.hpp" +#include "messages/MessageElement.hpp" +#include "providers/twitch/PubsubActions.hpp" #include "singletons/Emotes.hpp" #include "singletons/Resources.hpp" #include "singletons/Theme.hpp" @@ -17,7 +20,7 @@ MessagePtr makeSystemMessage(const QString &text) } MessageBuilder::MessageBuilder() - : message_(std::make_unique()) + : message_(std::make_shared()) { } @@ -165,7 +168,9 @@ Message &MessageBuilder::message() MessagePtr MessageBuilder::release() { - return MessagePtr(this->message_.release()); + std::shared_ptr ptr; + this->message_.swap(ptr); + return ptr; } void MessageBuilder::append(std::unique_ptr element) diff --git a/src/messages/MessageBuilder.hpp b/src/messages/MessageBuilder.hpp index a2e78ec29..d3cbdb8d0 100644 --- a/src/messages/MessageBuilder.hpp +++ b/src/messages/MessageBuilder.hpp @@ -1,11 +1,15 @@ #pragma once -#include "messages/Message.hpp" +#include "messages/MessageElement.hpp" #include #include namespace chatterino { +struct BanAction; +struct UnbanAction; +struct Message; +using MessagePtr = std::shared_ptr; struct SystemMessageTag { }; @@ -56,7 +60,7 @@ public: } private: - std::unique_ptr message_; + std::shared_ptr message_; }; } // namespace chatterino diff --git a/src/messages/MessageColor.cpp b/src/messages/MessageColor.cpp index f22654b51..fe793b30e 100644 --- a/src/messages/MessageColor.cpp +++ b/src/messages/MessageColor.cpp @@ -1,5 +1,7 @@ #include "MessageColor.hpp" +#include "singletons/Theme.hpp" + namespace chatterino { MessageColor::MessageColor(const QColor &color) diff --git a/src/messages/MessageColor.hpp b/src/messages/MessageColor.hpp index e7d828745..dd19692c9 100644 --- a/src/messages/MessageColor.hpp +++ b/src/messages/MessageColor.hpp @@ -1,10 +1,9 @@ #pragma once -#include "singletons/Theme.hpp" - #include namespace chatterino { +class Theme; struct MessageColor { enum Type { Custom, Text, Link, System }; diff --git a/src/messages/MessageElement.cpp b/src/messages/MessageElement.cpp index e31076107..b9271d2ed 100644 --- a/src/messages/MessageElement.cpp +++ b/src/messages/MessageElement.cpp @@ -3,9 +3,11 @@ #include "Application.hpp" #include "controllers/moderationactions/ModerationActions.hpp" #include "debug/Benchmark.hpp" +#include "messages/Emote.hpp" #include "messages/layouts/MessageLayoutContainer.hpp" #include "messages/layouts/MessageLayoutElement.hpp" #include "singletons/Settings.hpp" +#include "singletons/Theme.hpp" #include "util/DebugCount.hpp" namespace chatterino { @@ -223,8 +225,8 @@ void TimestampElement::addToContainer(MessageLayoutContainer &container, { if (flags.hasAny(this->getFlags())) { auto app = getApp(); - if (app->settings->timestampFormat != this->format_) { - this->format_ = app->settings->timestampFormat.getValue(); + if (getSettings()->timestampFormat != this->format_) { + this->format_ = getSettings()->timestampFormat.getValue(); this->element_.reset(this->formatTime(this->time_)); } @@ -236,7 +238,7 @@ TextElement *TimestampElement::formatTime(const QTime &time) { static QLocale locale("en_US"); - QString format = locale.toString(time, getApp()->settings->timestampFormat); + QString format = locale.toString(time, getSettings()->timestampFormat); return new TextElement(format, MessageElementFlag::Timestamp, MessageColor::System, FontStyle::ChatMedium); diff --git a/src/messages/MessageElement.hpp b/src/messages/MessageElement.hpp index 79f222789..8d96d1135 100644 --- a/src/messages/MessageElement.hpp +++ b/src/messages/MessageElement.hpp @@ -1,8 +1,6 @@ #pragma once #include "common/FlagsEnum.hpp" -#include "messages/Emote.hpp" -#include "messages/Image.hpp" #include "messages/Link.hpp" #include "messages/MessageColor.hpp" #include "singletons/Fonts.hpp" @@ -11,14 +9,20 @@ #include #include #include - #include #include +#include namespace chatterino { class Channel; struct MessageLayoutContainer; +class Image; +using ImagePtr = std::shared_ptr; + +struct Emote; +using EmotePtr = std::shared_ptr; + enum class MessageElementFlag { None = 0, Misc = (1 << 0), diff --git a/src/messages/MessageParseArgs.hpp b/src/messages/MessageParseArgs.hpp index 519262dc1..471d1d648 100644 --- a/src/messages/MessageParseArgs.hpp +++ b/src/messages/MessageParseArgs.hpp @@ -2,5 +2,4 @@ namespace chatterino { - } // namespace chatterino diff --git a/src/messages/layouts/MessageLayout.cpp b/src/messages/layouts/MessageLayout.cpp index 9847c1950..5fbbe0bfa 100644 --- a/src/messages/layouts/MessageLayout.cpp +++ b/src/messages/layouts/MessageLayout.cpp @@ -2,8 +2,12 @@ #include "Application.hpp" #include "debug/Benchmark.hpp" +#include "messages/Message.hpp" +#include "messages/MessageElement.hpp" +#include "messages/layouts/MessageLayoutContainer.hpp" #include "singletons/Emotes.hpp" #include "singletons/Settings.hpp" +#include "singletons/Theme.hpp" #include "singletons/WindowManager.hpp" #include "util/DebugCount.hpp" @@ -24,6 +28,7 @@ namespace chatterino { MessageLayout::MessageLayout(MessagePtr message) : message_(message) , buffer_(nullptr) + , container_(std::make_shared()) { DebugCount::increase("message layout"); } @@ -41,7 +46,7 @@ const Message *MessageLayout::getMessage() // Height int MessageLayout::getHeight() const { - return container_.getHeight(); + return container_->getHeight(); } // Layout @@ -68,7 +73,7 @@ bool MessageLayout::layout(int width, float scale, MessageElementFlags flags) // check if work mask changed layoutRequired |= this->currentWordFlags_ != flags; - this->currentWordFlags_ = flags; // app->settings->getWordTypeMask(); + this->currentWordFlags_ = flags; // getSettings()->getWordTypeMask(); // check if layout was requested manually layoutRequired |= this->flags.has(MessageLayoutFlag::RequiresLayout); @@ -82,9 +87,9 @@ bool MessageLayout::layout(int width, float scale, MessageElementFlags flags) return false; } - int oldHeight = this->container_.getHeight(); + int oldHeight = this->container_->getHeight(); this->actuallyLayout(width, flags); - if (widthChanged || this->container_.getHeight() != oldHeight) { + if (widthChanged || this->container_->getHeight() != oldHeight) { this->deleteBuffer(); } this->invalidateBuffer(); @@ -103,22 +108,22 @@ void MessageLayout::actuallyLayout(int width, MessageElementFlags _flags) messageFlags.unset(MessageFlag::Collapsed); } - this->container_.begin(width, this->scale_, messageFlags); + this->container_->begin(width, this->scale_, messageFlags); for (const auto &element : this->message_->elements) { - element->addToContainer(this->container_, _flags); + element->addToContainer(*this->container_, _flags); } - if (this->height_ != this->container_.getHeight()) { + if (this->height_ != this->container_->getHeight()) { this->deleteBuffer(); } - this->container_.end(); - this->height_ = this->container_.getHeight(); + this->container_->end(); + this->height_ = this->container_->getHeight(); // collapsed state this->flags.unset(MessageLayoutFlag::Collapsed); - if (this->container_.isCollapsed()) { + if (this->container_->isCollapsed()) { this->flags.set(MessageLayoutFlag::Collapsed); } } @@ -135,11 +140,12 @@ void MessageLayout::paint(QPainter &painter, int width, int y, int messageIndex, if (!pixmap) { #ifdef Q_OS_MACOS pixmap = new QPixmap(int(width * painter.device()->devicePixelRatioF()), - int(container_.getHeight() * + int(container_->getHeight() * painter.device()->devicePixelRatioF())); pixmap->setDevicePixelRatio(painter.device()->devicePixelRatioF()); #else - pixmap = new QPixmap(width, std::max(16, this->container_.getHeight())); + pixmap = + new QPixmap(width, std::max(16, this->container_->getHeight())); #endif this->buffer_ = std::shared_ptr(pixmap); @@ -157,7 +163,7 @@ void MessageLayout::paint(QPainter &painter, int width, int y, int messageIndex, // this->container.getHeight(), *pixmap); // draw gif emotes - this->container_.paintAnimatedElements(painter, y); + this->container_->paintAnimatedElements(painter, y); // draw disabled if (this->message_->flags.has(MessageFlag::Disabled)) { @@ -167,12 +173,12 @@ void MessageLayout::paint(QPainter &painter, int width, int y, int messageIndex, // draw selection if (!selection.isEmpty()) { - this->container_.paintSelection(painter, messageIndex, selection, y); + this->container_->paintSelection(painter, messageIndex, selection, y); } // draw message seperation line - if (app->settings->separateMessages.getValue()) { - painter.fillRect(0, y, this->container_.getWidth(), 1, + if (getSettings()->separateMessages.getValue()) { + painter.fillRect(0, y, this->container_->getWidth(), 1, app->themes->splits.messageSeperator); } @@ -184,9 +190,9 @@ void MessageLayout::paint(QPainter &painter, int width, int y, int messageIndex, : app->themes->tabs.selected.backgrounds.unfocused.color(); QBrush brush(color, static_cast( - app->settings->lastMessagePattern.getValue())); + getSettings()->lastMessagePattern.getValue())); - painter.fillRect(0, y + this->container_.getHeight() - 1, + painter.fillRect(0, y + this->container_->getHeight() - 1, pixmap->width(), 1, brush); } @@ -208,7 +214,7 @@ void MessageLayout::updateBuffer(QPixmap *buffer, int /*messageIndex*/, backgroundColor = app->themes->messages.backgrounds.highlighted; } else if (this->message_->flags.has(MessageFlag::Subscription)) { backgroundColor = app->themes->messages.backgrounds.subscription; - } else if (app->settings->alternateMessageBackground.getValue() && + } else if (getSettings()->alternateMessageBackground.getValue() && this->flags.has(MessageLayoutFlag::AlternateBackground)) { backgroundColor = app->themes->messages.backgrounds.alternate; } else { @@ -217,7 +223,7 @@ void MessageLayout::updateBuffer(QPixmap *buffer, int /*messageIndex*/, painter.fillRect(buffer->rect(), backgroundColor); // draw message - this->container_.paintElements(painter); + this->container_->paintElements(painter); #ifdef FOURTF // debug @@ -252,7 +258,7 @@ void MessageLayout::deleteCache() this->deleteBuffer(); #ifdef XD - this->container_.clear(); + this->container_->clear(); #endif } @@ -265,22 +271,23 @@ void MessageLayout::deleteCache() const MessageLayoutElement *MessageLayout::getElementAt(QPoint point) { // go through all words and return the first one that contains the point. - return this->container_.getElementAt(point); + return this->container_->getElementAt(point); } int MessageLayout::getLastCharacterIndex() const { - return this->container_.getLastCharacterIndex(); + return this->container_->getLastCharacterIndex(); } int MessageLayout::getSelectionIndex(QPoint position) { - return this->container_.getSelectionIndex(position); + return this->container_->getSelectionIndex(position); } -void MessageLayout::addSelectionText(QString &str, int from, int to) +void MessageLayout::addSelectionText(QString &str, int from, int to, + CopyMode copymode) { - this->container_.addSelectionText(str, from, to); + this->container_->addSelectionText(str, from, to, copymode); } } // namespace chatterino diff --git a/src/messages/layouts/MessageLayout.hpp b/src/messages/layouts/MessageLayout.hpp index 0f0d5cab3..671d30668 100644 --- a/src/messages/layouts/MessageLayout.hpp +++ b/src/messages/layouts/MessageLayout.hpp @@ -1,19 +1,25 @@ #pragma once +#include "common/Common.hpp" #include "common/FlagsEnum.hpp" -#include "messages/Message.hpp" -#include "messages/Selection.hpp" -#include "messages/layouts/MessageLayoutContainer.hpp" -#include "messages/layouts/MessageLayoutElement.hpp" #include - #include #include #include namespace chatterino { +struct Message; +using MessagePtr = std::shared_ptr; + +struct Selection; +struct MessageLayoutContainer; +class MessageLayoutElement; + +enum class MessageElementFlag; +using MessageElementFlags = FlagsEnum; + enum class MessageLayoutFlag : uint8_t { RequiresBufferUpdate = 1 << 1, RequiresLayout = 1 << 2, @@ -49,7 +55,8 @@ public: const MessageLayoutElement *getElementAt(QPoint point); int getLastCharacterIndex() const; int getSelectionIndex(QPoint position); - void addSelectionText(QString &str, int from = 0, int to = INT_MAX); + void addSelectionText(QString &str, int from = 0, int to = INT_MAX, + CopyMode copymode = CopyMode::Everything); // Misc bool isDisabled() const; @@ -57,7 +64,7 @@ public: private: // variables MessagePtr message_; - MessageLayoutContainer container_; + std::shared_ptr container_; std::shared_ptr buffer_ = nullptr; bool bufferValid_ = false; diff --git a/src/messages/layouts/MessageLayoutContainer.cpp b/src/messages/layouts/MessageLayoutContainer.cpp index 699cfc604..0aef73c6b 100644 --- a/src/messages/layouts/MessageLayoutContainer.cpp +++ b/src/messages/layouts/MessageLayoutContainer.cpp @@ -1,16 +1,20 @@ #include "MessageLayoutContainer.hpp" #include "Application.hpp" -#include "MessageLayoutElement.hpp" +#include "messages/Message.hpp" +#include "messages/MessageElement.hpp" #include "messages/Selection.hpp" +#include "messages/layouts/MessageLayoutElement.hpp" +#include "singletons/Fonts.hpp" #include "singletons/Settings.hpp" +#include "singletons/Theme.hpp" #include #include #define COMPACT_EMOTES_OFFSET 6 #define MAX_UNCOLLAPSED_LINES \ - (getApp()->settings->collpseMessagesMinLines.getValue()) + (getSettings()->collpseMessagesMinLines.getValue()) namespace chatterino { @@ -231,7 +235,7 @@ void MessageLayoutContainer::end() bool MessageLayoutContainer::canCollapse() { - return getApp()->settings->collpseMessagesMinLines.getValue() > 0 && + return getSettings()->collpseMessagesMinLines.getValue() > 0 && this->flags_.has(MessageFlag::Collapsed); } @@ -501,33 +505,41 @@ int MessageLayoutContainer::getLastCharacterIndex() const return this->lines_.back().endCharIndex; } -void MessageLayoutContainer::addSelectionText(QString &str, int from, int to) +void MessageLayoutContainer::addSelectionText(QString &str, int from, int to, + CopyMode copymode) { int index = 0; bool first = true; - for (std::unique_ptr &ele : this->elements_) { - int c = ele->getSelectionIndexCount(); + for (auto &element : this->elements_) { + if (copymode == CopyMode::OnlyTextAndEmotes) { + if (element->getCreator().getFlags().hasAny( + {MessageElementFlag::Timestamp, + MessageElementFlag::Username, MessageElementFlag::Badges})) + continue; + } + + auto indexCount = element->getSelectionIndexCount(); if (first) { - if (index + c > from) { - ele->addCopyTextToString(str, from - index, to - index); + if (index + indexCount > from) { + element->addCopyTextToString(str, from - index, to - index); first = false; - if (index + c > to) { + if (index + indexCount > to) { break; } } } else { - if (index + c > to) { - ele->addCopyTextToString(str, 0, to - index); + if (index + indexCount > to) { + element->addCopyTextToString(str, 0, to - index); break; } else { - ele->addCopyTextToString(str); + element->addCopyTextToString(str); } } - index += c; + index += indexCount; } } diff --git a/src/messages/layouts/MessageLayoutContainer.hpp b/src/messages/layouts/MessageLayoutContainer.hpp index f52f181e6..669986196 100644 --- a/src/messages/layouts/MessageLayoutContainer.hpp +++ b/src/messages/layouts/MessageLayoutContainer.hpp @@ -1,18 +1,21 @@ #pragma once -#include -#include - #include #include +#include +#include -#include "messages/Message.hpp" +#include "common/Common.hpp" +#include "common/FlagsEnum.hpp" #include "messages/Selection.hpp" +#include "messages/layouts/MessageLayoutElement.hpp" class QPainter; namespace chatterino { -class MessageLayoutElement; + +enum class MessageFlag : uint16_t; +using MessageFlags = FlagsEnum; struct Margin { int top; @@ -72,7 +75,7 @@ struct MessageLayoutContainer { // selection int getSelectionIndex(QPoint point); int getLastCharacterIndex() const; - void addSelectionText(QString &str, int from, int to); + void addSelectionText(QString &str, int from, int to, CopyMode copymode); bool isCollapsed(); @@ -92,7 +95,7 @@ private: // variables float scale_ = 1.f; int width_ = 0; - MessageFlags flags_ = MessageFlag::None; + MessageFlags flags_{}; int line_ = 0; int height_ = 0; int currentX_ = 0; diff --git a/src/messages/layouts/MessageLayoutElement.cpp b/src/messages/layouts/MessageLayoutElement.cpp index e8d333e2b..385451141 100644 --- a/src/messages/layouts/MessageLayoutElement.cpp +++ b/src/messages/layouts/MessageLayoutElement.cpp @@ -1,7 +1,9 @@ #include "messages/layouts/MessageLayoutElement.hpp" #include "Application.hpp" +#include "messages/Image.hpp" #include "messages/MessageElement.hpp" +#include "singletons/Theme.hpp" #include "util/DebugCount.hpp" #include diff --git a/src/messages/layouts/MessageLayoutElement.hpp b/src/messages/layouts/MessageLayoutElement.hpp index 44b4c9316..7167adaa9 100644 --- a/src/messages/layouts/MessageLayoutElement.hpp +++ b/src/messages/layouts/MessageLayoutElement.hpp @@ -3,19 +3,19 @@ #include #include #include - #include #include -#include "messages/Image.hpp" #include "messages/Link.hpp" #include "messages/MessageColor.hpp" -#include "singletons/Fonts.hpp" class QPainter; namespace chatterino { class MessageElement; +class Image; +using ImagePtr = std::shared_ptr; +enum class FontStyle : uint8_t; class MessageLayoutElement : boost::noncopyable { diff --git a/src/providers/bttv/BttvEmotes.cpp b/src/providers/bttv/BttvEmotes.cpp index 1d9115c09..2efe00d64 100644 --- a/src/providers/bttv/BttvEmotes.cpp +++ b/src/providers/bttv/BttvEmotes.cpp @@ -1,7 +1,9 @@ #include "providers/bttv/BttvEmotes.hpp" +#include "common/Common.hpp" #include "common/NetworkRequest.hpp" #include "debug/Log.hpp" +#include "messages/Emote.hpp" #include "messages/Image.hpp" #include "messages/ImageSet.hpp" #include "providers/twitch/TwitchChannel.hpp" @@ -11,73 +13,76 @@ namespace chatterino { namespace { -Url getEmoteLink(QString urlTemplate, const EmoteId &id, - const QString &emoteScale) -{ - urlTemplate.detach(); + Url getEmoteLink(QString urlTemplate, const EmoteId &id, + const QString &emoteScale) + { + urlTemplate.detach(); - return {urlTemplate.replace("{{id}}", id.string) - .replace("{{image}}", emoteScale)}; -} -std::pair parseGlobalEmotes(const QJsonObject &jsonRoot, - const EmoteMap ¤tEmotes) -{ - auto emotes = EmoteMap(); - auto jsonEmotes = jsonRoot.value("emotes").toArray(); - auto urlTemplate = qS("https:") + jsonRoot.value("urlTemplate").toString(); - - for (auto jsonEmote : jsonEmotes) { - auto id = EmoteId{jsonEmote.toObject().value("id").toString()}; - auto name = EmoteName{jsonEmote.toObject().value("code").toString()}; - - auto emote = Emote( - {name, - ImageSet{ - Image::fromUrl(getEmoteLink(urlTemplate, id, "1x"), 1), - Image::fromUrl(getEmoteLink(urlTemplate, id, "2x"), 0.5), - Image::fromUrl(getEmoteLink(urlTemplate, id, "3x"), 0.25)}, - Tooltip{name.string + "
Global Bttv Emote"}, - Url{"https://manage.betterttv.net/emotes/" + id.string}}); - - emotes[name] = cachedOrMakeEmotePtr(std::move(emote), currentEmotes); + return {urlTemplate.replace("{{id}}", id.string) + .replace("{{image}}", emoteScale)}; } + std::pair parseGlobalEmotes( + const QJsonObject &jsonRoot, const EmoteMap ¤tEmotes) + { + auto emotes = EmoteMap(); + auto jsonEmotes = jsonRoot.value("emotes").toArray(); + auto urlTemplate = + qS("https:") + jsonRoot.value("urlTemplate").toString(); - return {Success, std::move(emotes)}; -} -EmotePtr cachedOrMake(Emote &&emote, const EmoteId &id) -{ - static std::unordered_map> cache; - static std::mutex mutex; + for (auto jsonEmote : jsonEmotes) { + auto id = EmoteId{jsonEmote.toObject().value("id").toString()}; + auto name = + EmoteName{jsonEmote.toObject().value("code").toString()}; - return cachedOrMakeEmotePtr(std::move(emote), cache, mutex, id); -} -std::pair parseChannelEmotes(const QJsonObject &jsonRoot) -{ - auto emotes = EmoteMap(); - auto jsonEmotes = jsonRoot.value("emotes").toArray(); - auto urlTemplate = "https:" + jsonRoot.value("urlTemplate").toString(); + auto emote = Emote( + {name, + ImageSet{ + Image::fromUrl(getEmoteLink(urlTemplate, id, "1x"), 1), + Image::fromUrl(getEmoteLink(urlTemplate, id, "2x"), 0.5), + Image::fromUrl(getEmoteLink(urlTemplate, id, "3x"), 0.25)}, + Tooltip{name.string + "
Global Bttv Emote"}, + Url{"https://manage.betterttv.net/emotes/" + id.string}}); - for (auto jsonEmote_ : jsonEmotes) { - auto jsonEmote = jsonEmote_.toObject(); + emotes[name] = + cachedOrMakeEmotePtr(std::move(emote), currentEmotes); + } - auto id = EmoteId{jsonEmote.value("id").toString()}; - auto name = EmoteName{jsonEmote.value("code").toString()}; - // emoteObject.value("imageType").toString(); - - auto emote = Emote( - {name, - ImageSet{ - Image::fromUrl(getEmoteLink(urlTemplate, id, "1x"), 1), - Image::fromUrl(getEmoteLink(urlTemplate, id, "2x"), 0.5), - Image::fromUrl(getEmoteLink(urlTemplate, id, "3x"), 0.25)}, - Tooltip{name.string + "
Channel Bttv Emote"}, - Url{"https://manage.betterttv.net/emotes/" + id.string}}); - - emotes[name] = cachedOrMake(std::move(emote), id); + return {Success, std::move(emotes)}; } + EmotePtr cachedOrMake(Emote &&emote, const EmoteId &id) + { + static std::unordered_map> cache; + static std::mutex mutex; - return {Success, std::move(emotes)}; -} + return cachedOrMakeEmotePtr(std::move(emote), cache, mutex, id); + } + std::pair parseChannelEmotes(const QJsonObject &jsonRoot) + { + auto emotes = EmoteMap(); + auto jsonEmotes = jsonRoot.value("emotes").toArray(); + auto urlTemplate = "https:" + jsonRoot.value("urlTemplate").toString(); + + for (auto jsonEmote_ : jsonEmotes) { + auto jsonEmote = jsonEmote_.toObject(); + + auto id = EmoteId{jsonEmote.value("id").toString()}; + auto name = EmoteName{jsonEmote.value("code").toString()}; + // emoteObject.value("imageType").toString(); + + auto emote = Emote( + {name, + ImageSet{ + Image::fromUrl(getEmoteLink(urlTemplate, id, "1x"), 1), + Image::fromUrl(getEmoteLink(urlTemplate, id, "2x"), 0.5), + Image::fromUrl(getEmoteLink(urlTemplate, id, "3x"), 0.25)}, + Tooltip{name.string + "
Channel Bttv Emote"}, + Url{"https://manage.betterttv.net/emotes/" + id.string}}); + + emotes[name] = cachedOrMake(std::move(emote), id); + } + + return {Success, std::move(emotes)}; + } } // namespace // @@ -88,12 +93,12 @@ BttvEmotes::BttvEmotes() { } -std::shared_ptr BttvEmotes::global() const +std::shared_ptr BttvEmotes::emotes() const { return this->global_.get(); } -boost::optional BttvEmotes::global(const EmoteName &name) const +boost::optional BttvEmotes::emote(const EmoteName &name) const { auto emotes = this->global_.get(); auto it = emotes->find(name); @@ -102,7 +107,7 @@ boost::optional BttvEmotes::global(const EmoteName &name) const return it->second; } -void BttvEmotes::loadGlobal() +void BttvEmotes::loadEmotes() { auto request = NetworkRequest(QString(globalEmoteApiUrl)); diff --git a/src/providers/bttv/BttvEmotes.hpp b/src/providers/bttv/BttvEmotes.hpp index 7392cd6bc..c82cb67a4 100644 --- a/src/providers/bttv/BttvEmotes.hpp +++ b/src/providers/bttv/BttvEmotes.hpp @@ -1,11 +1,16 @@ #pragma once #include +#include "boost/optional.hpp" +#include "common/Aliases.hpp" #include "common/Atomic.hpp" -#include "messages/Emote.hpp" namespace chatterino { +struct Emote; +using EmotePtr = std::shared_ptr; +class EmoteMap; + class BttvEmotes final { static constexpr const char *globalEmoteApiUrl = @@ -16,9 +21,9 @@ class BttvEmotes final public: BttvEmotes(); - std::shared_ptr global() const; - boost::optional global(const EmoteName &name) const; - void loadGlobal(); + std::shared_ptr emotes() const; + boost::optional emote(const EmoteName &name) const; + void loadEmotes(); static void loadChannel(const QString &channelName, std::function callback); diff --git a/src/providers/chatterino/ChatterinoBadges.cpp b/src/providers/chatterino/ChatterinoBadges.cpp index 98b1841d4..37dc8f6f0 100644 --- a/src/providers/chatterino/ChatterinoBadges.cpp +++ b/src/providers/chatterino/ChatterinoBadges.cpp @@ -14,39 +14,41 @@ ChatterinoBadges::ChatterinoBadges() boost::optional ChatterinoBadges::getBadge(const UserName &username) { - return this->badges.access()->get(username); + return boost::none; + // return this->badges.access()->get(username); } void ChatterinoBadges::loadChatterinoBadges() { - static QString url("https://fourtf.com/chatterino/badges.json"); + // static QString url("https://fourtf.com/chatterino/badges.json"); - NetworkRequest req(url); - req.setCaller(QThread::currentThread()); + // NetworkRequest req(url); + // req.setCaller(QThread::currentThread()); - req.onSuccess([this](auto result) { - auto jsonRoot = result.parseJson(); - auto badges = this->badges.access(); - auto replacement = badges->makeReplacment(); + // req.onSuccess([this](auto result) { + // auto jsonRoot = result.parseJson(); + // auto badges = this->badges.access(); + // auto replacement = badges->makeReplacment(); - for (auto jsonBadge_ : jsonRoot.value("badges").toArray()) { - auto jsonBadge = jsonBadge_.toObject(); + // for (auto jsonBadge_ : jsonRoot.value("badges").toArray()) { + // auto jsonBadge = jsonBadge_.toObject(); - auto emote = Emote{ - EmoteName{}, ImageSet{Url{jsonBadge.value("image").toString()}}, - Tooltip{jsonBadge.value("tooltip").toString()}, Url{}}; + // auto emote = Emote{ + // EmoteName{}, + // ImageSet{Url{jsonBadge.value("image").toString()}}, + // Tooltip{jsonBadge.value("tooltip").toString()}, Url{}}; - for (auto jsonUser : jsonBadge.value("users").toArray()) { - replacement.add(UserName{jsonUser.toString()}, - std::move(emote)); - } - } + // for (auto jsonUser : jsonBadge.value("users").toArray()) { + // replacement.add(UserName{jsonUser.toString()}, + // std::move(emote)); + // } + // } - replacement.apply(); - return Success; - }); + // replacement.apply(); + // return Success; + //}); - req.execute(); + // req.execute(); } } // namespace chatterino diff --git a/src/providers/chatterino/ChatterinoBadges.hpp b/src/providers/chatterino/ChatterinoBadges.hpp index 3162c75b1..89870fd16 100644 --- a/src/providers/chatterino/ChatterinoBadges.hpp +++ b/src/providers/chatterino/ChatterinoBadges.hpp @@ -1,14 +1,14 @@ #pragma once #include -#include -#include "common/Common.hpp" -#include "common/UniqueAccess.hpp" -#include "messages/Emote.hpp" -#include "messages/EmoteCache.hpp" + +#include "common/Aliases.hpp" namespace chatterino { +struct Emote; +using EmotePtr = std::shared_ptr; + class ChatterinoBadges { public: @@ -19,7 +19,7 @@ public: private: void loadChatterinoBadges(); - UniqueAccess> badges; + // UniqueAccess> badges; }; } // namespace chatterino diff --git a/src/providers/emoji/Emojis.cpp b/src/providers/emoji/Emojis.cpp index 06d4e798b..809b7c3d5 100644 --- a/src/providers/emoji/Emojis.cpp +++ b/src/providers/emoji/Emojis.cpp @@ -2,6 +2,7 @@ #include "Application.hpp" #include "debug/Log.hpp" +#include "messages/Emote.hpp" #include "singletons/Settings.hpp" #include @@ -12,82 +13,81 @@ #include namespace chatterino { - namespace { + void parseEmoji(const std::shared_ptr &emojiData, + const rapidjson::Value &unparsedEmoji, + QString shortCode = QString()) + { + static uint unicodeBytes[4]; -void parseEmoji(const std::shared_ptr &emojiData, - const rapidjson::Value &unparsedEmoji, - QString shortCode = QString()) -{ - static uint unicodeBytes[4]; + struct { + bool apple; + bool google; + bool twitter; + bool emojione; + bool facebook; + bool messenger; + } capabilities; - struct { - bool apple; - bool google; - bool twitter; - bool emojione; - bool facebook; - bool messenger; - } capabilities; - - if (!shortCode.isEmpty()) { - emojiData->shortCodes.push_back(shortCode); - } else { - const auto &shortCodes = unparsedEmoji["short_names"]; - for (const auto &shortCode : shortCodes.GetArray()) { - emojiData->shortCodes.emplace_back(shortCode.GetString()); + if (!shortCode.isEmpty()) { + emojiData->shortCodes.push_back(shortCode); + } else { + const auto &shortCodes = unparsedEmoji["short_names"]; + for (const auto &shortCode : shortCodes.GetArray()) { + emojiData->shortCodes.emplace_back(shortCode.GetString()); + } } - } - rj::getSafe(unparsedEmoji, "non_qualified", emojiData->nonQualifiedCode); - rj::getSafe(unparsedEmoji, "unified", emojiData->unifiedCode); + rj::getSafe(unparsedEmoji, "non_qualified", + emojiData->nonQualifiedCode); + rj::getSafe(unparsedEmoji, "unified", emojiData->unifiedCode); - rj::getSafe(unparsedEmoji, "has_img_apple", capabilities.apple); - rj::getSafe(unparsedEmoji, "has_img_google", capabilities.google); - rj::getSafe(unparsedEmoji, "has_img_twitter", capabilities.twitter); - rj::getSafe(unparsedEmoji, "has_img_emojione", capabilities.emojione); - rj::getSafe(unparsedEmoji, "has_img_facebook", capabilities.facebook); - rj::getSafe(unparsedEmoji, "has_img_messenger", capabilities.messenger); + rj::getSafe(unparsedEmoji, "has_img_apple", capabilities.apple); + rj::getSafe(unparsedEmoji, "has_img_google", capabilities.google); + rj::getSafe(unparsedEmoji, "has_img_twitter", capabilities.twitter); + rj::getSafe(unparsedEmoji, "has_img_emojione", capabilities.emojione); + rj::getSafe(unparsedEmoji, "has_img_facebook", capabilities.facebook); + rj::getSafe(unparsedEmoji, "has_img_messenger", capabilities.messenger); - if (capabilities.apple) { - emojiData->capabilities.insert("Apple"); - } - if (capabilities.google) { - emojiData->capabilities.insert("Google"); - } - if (capabilities.twitter) { - emojiData->capabilities.insert("Twitter"); - } - if (capabilities.emojione) { - emojiData->capabilities.insert("EmojiOne 3"); - } - if (capabilities.facebook) { - emojiData->capabilities.insert("Facebook"); - } - if (capabilities.messenger) { - emojiData->capabilities.insert("Messenger"); - } + if (capabilities.apple) { + emojiData->capabilities.insert("Apple"); + } + if (capabilities.google) { + emojiData->capabilities.insert("Google"); + } + if (capabilities.twitter) { + emojiData->capabilities.insert("Twitter"); + } + if (capabilities.emojione) { + emojiData->capabilities.insert("EmojiOne 3"); + } + if (capabilities.facebook) { + emojiData->capabilities.insert("Facebook"); + } + if (capabilities.messenger) { + emojiData->capabilities.insert("Messenger"); + } - QStringList unicodeCharacters; - if (!emojiData->nonQualifiedCode.isEmpty()) { - unicodeCharacters = emojiData->nonQualifiedCode.toLower().split('-'); - } else { - unicodeCharacters = emojiData->unifiedCode.toLower().split('-'); + QStringList unicodeCharacters; + if (!emojiData->nonQualifiedCode.isEmpty()) { + unicodeCharacters = + emojiData->nonQualifiedCode.toLower().split('-'); + } else { + unicodeCharacters = emojiData->unifiedCode.toLower().split('-'); + } + if (unicodeCharacters.length() < 1) { + return; + } + + int numUnicodeBytes = 0; + + for (const QString &unicodeCharacter : unicodeCharacters) { + unicodeBytes[numUnicodeBytes++] = + QString(unicodeCharacter).toUInt(nullptr, 16); + } + + emojiData->value = QString::fromUcs4(unicodeBytes, numUnicodeBytes); } - if (unicodeCharacters.length() < 1) { - return; - } - - int numUnicodeBytes = 0; - - for (const QString &unicodeCharacter : unicodeCharacters) { - unicodeBytes[numUnicodeBytes++] = - QString(unicodeCharacter).toUInt(nullptr, 16); - } - - emojiData->value = QString::fromUcs4(unicodeBytes, numUnicodeBytes); -} - } // namespace void Emojis::load() @@ -103,12 +103,10 @@ void Emojis::load() void Emojis::loadEmojis() { - std::map toneNames; - toneNames["1F3FB"] = "tone1"; - toneNames["1F3FC"] = "tone2"; - toneNames["1F3FD"] = "tone3"; - toneNames["1F3FE"] = "tone4"; - toneNames["1F3FF"] = "tone5"; + auto toneNames = std::map{ + {"1F3FB", "tone1"}, {"1F3FC", "tone2"}, {"1F3FD", "tone3"}, + {"1F3FE", "tone4"}, {"1F3FF", "tone5"}, + }; QFile file(":/emoji.json"); file.open(QFile::ReadOnly); @@ -218,7 +216,7 @@ void Emojis::loadEmojiSet() { auto app = getApp(); - app->settings->emojiSet.connect([=](const auto &emojiSet, auto) { + getSettings()->emojiSet.connect([=](const auto &emojiSet, auto) { log("Using emoji set {}", emojiSet); this->emojis.each([=](const auto &name, std::shared_ptr &emoji) { @@ -233,27 +231,12 @@ void Emojis::loadEmojiSet() // {"Google", "https://cdn.jsdelivr.net/npm/emoji-datasource-google@4.0.4/img/google/64/"}, // {"Messenger", "https://cdn.jsdelivr.net/npm/emoji-datasource-messenger@4.0.4/img/messenger/64/"}, - // {"EmojiOne 2", "https://cdnjs.cloudflare.com/ajax/libs/emojione/2.2.6/assets/png/"}, - // {"EmojiOne 3", "https://braize.pajlada.com/emoji/img/emojione/64/"}, - // {"Twitter", "https://braize.pajlada.com/emoji/img/twitter/64/"}, - // {"Facebook", "https://braize.pajlada.com/emoji/img/facebook/64/"}, - // {"Apple", "https://braize.pajlada.com/emoji/img/apple/64/"}, - // {"Google", "https://braize.pajlada.com/emoji/img/google/64/"}, - // {"Messenger", "https://braize.pajlada.com/emoji/img/messenger/64/"}, - {"EmojiOne 3", "https://pajbot.com/static/emoji/img/emojione/64/"}, {"Twitter", "https://pajbot.com/static/emoji/img/twitter/64/"}, {"Facebook", "https://pajbot.com/static/emoji/img/facebook/64/"}, {"Apple", "https://pajbot.com/static/emoji/img/apple/64/"}, {"Google", "https://pajbot.com/static/emoji/img/google/64/"}, {"Messenger", "https://pajbot.com/static/emoji/img/messenger/64/"}, - -// {"EmojiOne 3", "https://cdn.fourtf.com/emoji/emojione/64/"}, -// {"Twitter", "https://cdn.fourtf.com/emoji/twitter/64/"}, -// {"Facebook", "https://cdn.fourtf.com/emoji/facebook/64/"}, -// {"Apple", "https://cdn.fourtf.com/emoji/apple/64/"}, -// {"Google", "https://cdn.fourtf.com/emoji/google/64/"}, -// {"Messenger", "https://cdn.fourtf.com/emoji/messenger/64/"}, }; // clang-format on diff --git a/src/providers/emoji/Emojis.hpp b/src/providers/emoji/Emojis.hpp index 18bd33522..62443b9aa 100644 --- a/src/providers/emoji/Emojis.hpp +++ b/src/providers/emoji/Emojis.hpp @@ -1,6 +1,5 @@ #pragma once -#include "messages/Emote.hpp" #include "util/ConcurrentMap.hpp" #include @@ -12,6 +11,9 @@ namespace chatterino { +struct Emote; +using EmotePtr = std::shared_ptr; + struct EmojiData { // actual byte-representation of the emoji (i.e. \154075\156150 which is // :male:) diff --git a/src/providers/ffz/FfzEmotes.cpp b/src/providers/ffz/FfzEmotes.cpp index 24341a817..eb6912ff7 100644 --- a/src/providers/ffz/FfzEmotes.cpp +++ b/src/providers/ffz/FfzEmotes.cpp @@ -3,104 +3,107 @@ #include #include "common/NetworkRequest.hpp" +#include "common/Outcome.hpp" #include "debug/Log.hpp" +#include "messages/Emote.hpp" #include "messages/Image.hpp" namespace chatterino { namespace { -Url getEmoteLink(const QJsonObject &urls, const QString &emoteScale) -{ - auto emote = urls.value(emoteScale); - if (emote.isUndefined()) { - return {""}; - } - - assert(emote.isString()); - - return {"https:" + emote.toString()}; -} -void fillInEmoteData(const QJsonObject &urls, const EmoteName &name, - const QString &tooltip, Emote &emoteData) -{ - auto url1x = getEmoteLink(urls, "1"); - auto url2x = getEmoteLink(urls, "2"); - auto url3x = getEmoteLink(urls, "4"); - - //, code, tooltip - emoteData.name = name; - emoteData.images = - ImageSet{Image::fromUrl(url1x, 1), Image::fromUrl(url2x, 0.5), - Image::fromUrl(url3x, 0.25)}; - emoteData.tooltip = {tooltip}; -} -EmotePtr cachedOrMake(Emote &&emote, const EmoteId &id) -{ - static std::unordered_map> cache; - static std::mutex mutex; - - return cachedOrMakeEmotePtr(std::move(emote), cache, mutex, id); -} -std::pair parseGlobalEmotes(const QJsonObject &jsonRoot, - const EmoteMap ¤tEmotes) -{ - auto jsonSets = jsonRoot.value("sets").toObject(); - auto emotes = EmoteMap(); - - for (auto jsonSet : jsonSets) { - auto jsonEmotes = jsonSet.toObject().value("emoticons").toArray(); - - for (auto jsonEmoteValue : jsonEmotes) { - auto jsonEmote = jsonEmoteValue.toObject(); - - auto name = EmoteName{jsonEmote.value("name").toString()}; - auto id = EmoteId{jsonEmote.value("id").toString()}; - auto urls = jsonEmote.value("urls").toObject(); - - auto emote = Emote(); - fillInEmoteData(urls, name, name.string + "
Global FFZ Emote", - emote); - emote.homePage = - Url{QString("https://www.frankerfacez.com/emoticon/%1-%2") - .arg(id.string) - .arg(name.string)}; - - emotes[name] = - cachedOrMakeEmotePtr(std::move(emote), currentEmotes); + Url getEmoteLink(const QJsonObject &urls, const QString &emoteScale) + { + auto emote = urls.value(emoteScale); + if (emote.isUndefined()) { + return {""}; } + + assert(emote.isString()); + + return {"https:" + emote.toString()}; } + void fillInEmoteData(const QJsonObject &urls, const EmoteName &name, + const QString &tooltip, Emote &emoteData) + { + auto url1x = getEmoteLink(urls, "1"); + auto url2x = getEmoteLink(urls, "2"); + auto url3x = getEmoteLink(urls, "4"); - return {Success, std::move(emotes)}; -} -std::pair parseChannelEmotes(const QJsonObject &jsonRoot) -{ - auto jsonSets = jsonRoot.value("sets").toObject(); - auto emotes = EmoteMap(); + //, code, tooltip + emoteData.name = name; + emoteData.images = + ImageSet{Image::fromUrl(url1x, 1), Image::fromUrl(url2x, 0.5), + Image::fromUrl(url3x, 0.25)}; + emoteData.tooltip = {tooltip}; + } + EmotePtr cachedOrMake(Emote &&emote, const EmoteId &id) + { + static std::unordered_map> cache; + static std::mutex mutex; - for (auto jsonSet : jsonSets) { - auto jsonEmotes = jsonSet.toObject().value("emoticons").toArray(); + return cachedOrMakeEmotePtr(std::move(emote), cache, mutex, id); + } + std::pair parseGlobalEmotes( + const QJsonObject &jsonRoot, const EmoteMap ¤tEmotes) + { + auto jsonSets = jsonRoot.value("sets").toObject(); + auto emotes = EmoteMap(); - for (auto _jsonEmote : jsonEmotes) { - auto jsonEmote = _jsonEmote.toObject(); + for (auto jsonSet : jsonSets) { + auto jsonEmotes = jsonSet.toObject().value("emoticons").toArray(); - // margins - auto id = EmoteId{QString::number(jsonEmote.value("id").toInt())}; - auto name = EmoteName{jsonEmote.value("name").toString()}; - auto urls = jsonEmote.value("urls").toObject(); + for (auto jsonEmoteValue : jsonEmotes) { + auto jsonEmote = jsonEmoteValue.toObject(); - Emote emote; - fillInEmoteData(urls, name, name.string + "
Channel FFZ Emote", - emote); - emote.homePage = - Url{QString("https://www.frankerfacez.com/emoticon/%1-%2") - .arg(id.string) - .arg(name.string)}; + auto name = EmoteName{jsonEmote.value("name").toString()}; + auto id = EmoteId{jsonEmote.value("id").toString()}; + auto urls = jsonEmote.value("urls").toObject(); - emotes[name] = cachedOrMake(std::move(emote), id); + auto emote = Emote(); + fillInEmoteData(urls, name, + name.string + "
Global FFZ Emote", emote); + emote.homePage = + Url{QString("https://www.frankerfacez.com/emoticon/%1-%2") + .arg(id.string) + .arg(name.string)}; + + emotes[name] = + cachedOrMakeEmotePtr(std::move(emote), currentEmotes); + } } - } - return {Success, std::move(emotes)}; -} + return {Success, std::move(emotes)}; + } + std::pair parseChannelEmotes(const QJsonObject &jsonRoot) + { + auto jsonSets = jsonRoot.value("sets").toObject(); + auto emotes = EmoteMap(); + + for (auto jsonSet : jsonSets) { + auto jsonEmotes = jsonSet.toObject().value("emoticons").toArray(); + + for (auto _jsonEmote : jsonEmotes) { + auto jsonEmote = _jsonEmote.toObject(); + + // margins + auto id = + EmoteId{QString::number(jsonEmote.value("id").toInt())}; + auto name = EmoteName{jsonEmote.value("name").toString()}; + auto urls = jsonEmote.value("urls").toObject(); + + Emote emote; + fillInEmoteData(urls, name, + name.string + "
Channel FFZ Emote", emote); + emote.homePage = + Url{QString("https://www.frankerfacez.com/emoticon/%1-%2") + .arg(id.string) + .arg(name.string)}; + + emotes[name] = cachedOrMake(std::move(emote), id); + } + } + + return {Success, std::move(emotes)}; + } } // namespace FfzEmotes::FfzEmotes() @@ -108,12 +111,12 @@ FfzEmotes::FfzEmotes() { } -std::shared_ptr FfzEmotes::global() const +std::shared_ptr FfzEmotes::emotes() const { return this->global_.get(); } -boost::optional FfzEmotes::global(const EmoteName &name) const +boost::optional FfzEmotes::emote(const EmoteName &name) const { auto emotes = this->global_.get(); auto it = emotes->find(name); @@ -121,7 +124,7 @@ boost::optional FfzEmotes::global(const EmoteName &name) const return boost::none; } -void FfzEmotes::loadGlobal() +void FfzEmotes::loadEmotes() { QString url("https://api.frankerfacez.com/v1/set/global"); @@ -130,7 +133,7 @@ void FfzEmotes::loadGlobal() request.setTimeout(30000); request.onSuccess([this](auto result) -> Outcome { - auto emotes = this->global(); + auto emotes = this->emotes(); auto pair = parseGlobalEmotes(result.parseJson(), *emotes); if (pair.first) this->global_.set( diff --git a/src/providers/ffz/FfzEmotes.hpp b/src/providers/ffz/FfzEmotes.hpp index cc75b09bd..8214cbf80 100644 --- a/src/providers/ffz/FfzEmotes.hpp +++ b/src/providers/ffz/FfzEmotes.hpp @@ -1,24 +1,27 @@ #pragma once #include +#include "boost/optional.hpp" +#include "common/Aliases.hpp" #include "common/Atomic.hpp" -#include "messages/Emote.hpp" namespace chatterino { +struct Emote; +using EmotePtr = std::shared_ptr; +class EmoteMap; + class FfzEmotes final { static constexpr const char *globalEmoteApiUrl = "https://api.frankerfacez.com/v1/set/global"; - static constexpr const char *channelEmoteApiUrl = - "https://api.betterttv.net/2/channels/"; public: FfzEmotes(); - std::shared_ptr global() const; - boost::optional global(const EmoteName &name) const; - void loadGlobal(); + std::shared_ptr emotes() const; + boost::optional emote(const EmoteName &name) const; + void loadEmotes(); static void loadChannel(const QString &channelName, std::function callback); diff --git a/src/providers/irc/AbstractIrcServer.cpp b/src/providers/irc/AbstractIrcServer.cpp index b7995282f..25c810a4d 100644 --- a/src/providers/irc/AbstractIrcServer.cpp +++ b/src/providers/irc/AbstractIrcServer.cpp @@ -1,6 +1,8 @@ #include "AbstractIrcServer.hpp" +#include "common/Channel.hpp" #include "common/Common.hpp" +#include "debug/Log.hpp" #include "messages/LimitedQueueSnapshot.hpp" #include "messages/Message.hpp" #include "messages/MessageBuilder.hpp" diff --git a/src/providers/irc/AbstractIrcServer.hpp b/src/providers/irc/AbstractIrcServer.hpp index 580cdd0fe..109f869ef 100644 --- a/src/providers/irc/AbstractIrcServer.hpp +++ b/src/providers/irc/AbstractIrcServer.hpp @@ -1,6 +1,5 @@ #pragma once -#include "common/Channel.hpp" #include "providers/irc/IrcConnection2.hpp" #include @@ -11,6 +10,9 @@ namespace chatterino { +class Channel; +using ChannelPtr = std::shared_ptr; + class AbstractIrcServer { public: diff --git a/src/providers/twitch/IrcMessageHandler.cpp b/src/providers/twitch/IrcMessageHandler.cpp index 35c7c51d9..9aabbc91d 100644 --- a/src/providers/twitch/IrcMessageHandler.cpp +++ b/src/providers/twitch/IrcMessageHandler.cpp @@ -10,6 +10,7 @@ #include "providers/twitch/TwitchMessageBuilder.hpp" #include "providers/twitch/TwitchServer.hpp" #include "singletons/Resources.hpp" +#include "singletons/Settings.hpp" #include "singletons/WindowManager.hpp" #include "util/IrcHelpers.hpp" @@ -230,7 +231,7 @@ void IrcMessageHandler::handleWhisperMessage(Communi::IrcMessage *message) c->addMessage(_message); - if (app->settings->inlineWhispers) { + if (getSettings()->inlineWhispers) { app->twitch.server->forEachChannel([_message](ChannelPtr channel) { channel->addMessage(_message); // }); diff --git a/src/providers/twitch/PartialTwitchUser.cpp b/src/providers/twitch/PartialTwitchUser.cpp index c3d9e5102..6aca624b1 100644 --- a/src/providers/twitch/PartialTwitchUser.cpp +++ b/src/providers/twitch/PartialTwitchUser.cpp @@ -1,5 +1,6 @@ #include "providers/twitch/PartialTwitchUser.hpp" +#include "common/Common.hpp" #include "common/NetworkRequest.hpp" #include "debug/Log.hpp" #include "providers/twitch/TwitchCommon.hpp" diff --git a/src/providers/twitch/PubsubClient.cpp b/src/providers/twitch/PubsubClient.cpp index 7f591aebc..77b6ba1d4 100644 --- a/src/providers/twitch/PubsubClient.cpp +++ b/src/providers/twitch/PubsubClient.cpp @@ -24,157 +24,159 @@ static std::map sentMessages; namespace detail { -PubSubClient::PubSubClient(WebsocketClient &websocketClient, - WebsocketHandle handle) - : websocketClient_(websocketClient) - , handle_(handle) -{ -} - -void PubSubClient::start() -{ - assert(!this->started_); - - this->started_ = true; - - this->ping(); -} - -void PubSubClient::stop() -{ - assert(this->started_); - - this->started_ = false; -} - -bool PubSubClient::listen(rapidjson::Document &message) -{ - int numRequestedListens = message["data"]["topics"].Size(); - - if (this->numListens_ + numRequestedListens > MAX_PUBSUB_LISTENS) { - // This PubSubClient is already at its peak listens - return false; + PubSubClient::PubSubClient(WebsocketClient &websocketClient, + WebsocketHandle handle) + : websocketClient_(websocketClient) + , handle_(handle) + { } - this->numListens_ += numRequestedListens; + void PubSubClient::start() + { + assert(!this->started_); - for (const auto &topic : message["data"]["topics"].GetArray()) { - this->listeners_.emplace_back( - Listener{topic.GetString(), false, false, false}); + this->started_ = true; + + this->ping(); } - auto uuid = CreateUUID(); + void PubSubClient::stop() + { + assert(this->started_); - rj::set(message, "nonce", uuid); + this->started_ = false; + } - std::string payload = rj::stringify(message); - sentMessages[uuid] = payload; + bool PubSubClient::listen(rapidjson::Document &message) + { + int numRequestedListens = message["data"]["topics"].Size(); - this->send(payload.c_str()); - - return true; -} - -void PubSubClient::unlistenPrefix(const std::string &prefix) -{ - std::vector topics; - - for (auto it = this->listeners_.begin(); it != this->listeners_.end();) { - const auto &listener = *it; - if (listener.topic.find(prefix) == 0) { - topics.push_back(listener.topic); - it = this->listeners_.erase(it); - } else { - ++it; + if (this->numListens_ + numRequestedListens > MAX_PUBSUB_LISTENS) { + // This PubSubClient is already at its peak listens + return false; } - } - if (topics.empty()) { - return; - } + this->numListens_ += numRequestedListens; - auto message = createUnlistenMessage(topics); - - auto uuid = CreateUUID(); - - rj::set(message, "nonce", CreateUUID()); - - std::string payload = rj::stringify(message); - sentMessages[uuid] = payload; - - this->send(payload.c_str()); -} - -void PubSubClient::handlePong() -{ - assert(this->awaitingPong_); - - log("Got pong!"); - - this->awaitingPong_ = false; -} - -bool PubSubClient::isListeningToTopic(const std::string &payload) -{ - for (const auto &listener : this->listeners_) { - if (listener.topic == payload) { - return true; + for (const auto &topic : message["data"]["topics"].GetArray()) { + this->listeners_.emplace_back( + Listener{topic.GetString(), false, false, false}); } + + auto uuid = CreateUUID(); + + rj::set(message, "nonce", uuid); + + std::string payload = rj::stringify(message); + sentMessages[uuid] = payload; + + this->send(payload.c_str()); + + return true; } - return false; -} + void PubSubClient::unlistenPrefix(const std::string &prefix) + { + std::vector topics; -void PubSubClient::ping() -{ - assert(this->started_); + for (auto it = this->listeners_.begin(); + it != this->listeners_.end();) { + const auto &listener = *it; + if (listener.topic.find(prefix) == 0) { + topics.push_back(listener.topic); + it = this->listeners_.erase(it); + } else { + ++it; + } + } - if (!this->send(pingPayload)) { - return; + if (topics.empty()) { + return; + } + + auto message = createUnlistenMessage(topics); + + auto uuid = CreateUUID(); + + rj::set(message, "nonce", CreateUUID()); + + std::string payload = rj::stringify(message); + sentMessages[uuid] = payload; + + this->send(payload.c_str()); } - this->awaitingPong_ = true; + void PubSubClient::handlePong() + { + assert(this->awaitingPong_); - auto self = this->shared_from_this(); + log("Got pong!"); - runAfter(this->websocketClient_.get_io_service(), std::chrono::seconds(15), - [self](auto timer) { - if (!self->started_) { - return; - } + this->awaitingPong_ = false; + } - if (self->awaitingPong_) { - log("No pong respnose, disconnect!"); - // TODO(pajlada): Label this connection as "disconnect me" - } - }); - - runAfter(this->websocketClient_.get_io_service(), std::chrono::minutes(5), - [self](auto timer) { - if (!self->started_) { - return; - } - - self->ping(); // - }); -} - -bool PubSubClient::send(const char *payload) -{ - WebsocketErrorCode ec; - this->websocketClient_.send(this->handle_, payload, - websocketpp::frame::opcode::text, ec); - - if (ec) { - log("Error sending message {}: {}", payload, ec.message()); - // TODO(pajlada): Check which error code happened and maybe gracefully - // handle it + bool PubSubClient::isListeningToTopic(const std::string &payload) + { + for (const auto &listener : this->listeners_) { + if (listener.topic == payload) { + return true; + } + } return false; } - return true; -} + void PubSubClient::ping() + { + assert(this->started_); + + if (!this->send(pingPayload)) { + return; + } + + this->awaitingPong_ = true; + + auto self = this->shared_from_this(); + + runAfter(this->websocketClient_.get_io_service(), + std::chrono::seconds(15), [self](auto timer) { + if (!self->started_) { + return; + } + + if (self->awaitingPong_) { + log("No pong respnose, disconnect!"); + // TODO(pajlada): Label this connection as "disconnect + // me" + } + }); + + runAfter(this->websocketClient_.get_io_service(), + std::chrono::minutes(5), [self](auto timer) { + if (!self->started_) { + return; + } + + self->ping(); // + }); + } + + bool PubSubClient::send(const char *payload) + { + WebsocketErrorCode ec; + this->websocketClient_.send(this->handle_, payload, + websocketpp::frame::opcode::text, ec); + + if (ec) { + log("Error sending message {}: {}", payload, ec.message()); + // TODO(pajlada): Check which error code happened and maybe + // gracefully handle it + + return false; + } + + return true; + } } // namespace detail diff --git a/src/providers/twitch/PubsubClient.hpp b/src/providers/twitch/PubsubClient.hpp index 211502176..f3cc10f15 100644 --- a/src/providers/twitch/PubsubClient.hpp +++ b/src/providers/twitch/PubsubClient.hpp @@ -32,41 +32,42 @@ using WebsocketErrorCode = websocketpp::lib::error_code; namespace detail { -struct Listener { - std::string topic; - bool authed; - bool persistent; - bool confirmed = false; -}; + struct Listener { + std::string topic; + bool authed; + bool persistent; + bool confirmed = false; + }; -class PubSubClient : public std::enable_shared_from_this -{ -public: - PubSubClient(WebsocketClient &_websocketClient, WebsocketHandle _handle); + class PubSubClient : public std::enable_shared_from_this + { + public: + PubSubClient(WebsocketClient &_websocketClient, + WebsocketHandle _handle); - void start(); - void stop(); + void start(); + void stop(); - bool listen(rapidjson::Document &message); - void unlistenPrefix(const std::string &prefix); + bool listen(rapidjson::Document &message); + void unlistenPrefix(const std::string &prefix); - void handlePong(); + void handlePong(); - bool isListeningToTopic(const std::string &topic); + bool isListeningToTopic(const std::string &topic); -private: - void ping(); - bool send(const char *payload); + private: + void ping(); + bool send(const char *payload); - WebsocketClient &websocketClient_; - WebsocketHandle handle_; - uint16_t numListens_ = 0; + WebsocketClient &websocketClient_; + WebsocketHandle handle_; + uint16_t numListens_ = 0; - std::vector listeners_; + std::vector listeners_; - std::atomic awaitingPong_{false}; - std::atomic started_{false}; -}; + std::atomic awaitingPong_{false}; + std::atomic started_{false}; + }; } // namespace detail diff --git a/src/providers/twitch/PubsubHelpers.cpp b/src/providers/twitch/PubsubHelpers.cpp index cf4560f6b..f9b4e784a 100644 --- a/src/providers/twitch/PubsubHelpers.cpp +++ b/src/providers/twitch/PubsubHelpers.cpp @@ -1,6 +1,7 @@ #include "providers/twitch/PubsubHelpers.hpp" #include "providers/twitch/PubsubActions.hpp" +#include "providers/twitch/TwitchAccount.hpp" #include "util/RapidjsonHelpers.hpp" namespace chatterino { diff --git a/src/providers/twitch/PubsubHelpers.hpp b/src/providers/twitch/PubsubHelpers.hpp index 8b9b3f322..c478ed437 100644 --- a/src/providers/twitch/PubsubHelpers.hpp +++ b/src/providers/twitch/PubsubHelpers.hpp @@ -1,16 +1,14 @@ #pragma once -#include "debug/Log.hpp" -#include "providers/twitch/TwitchAccount.hpp" -#include "util/RapidjsonHelpers.hpp" - #include #include - #include +#include "debug/Log.hpp" +#include "util/RapidjsonHelpers.hpp" namespace chatterino { +class TwitchAccount; struct ActionUser; const rapidjson::Value &getArgs(const rapidjson::Value &data); diff --git a/src/providers/twitch/TwitchAccount.cpp b/src/providers/twitch/TwitchAccount.cpp index 0258c6e72..3e7ea237d 100644 --- a/src/providers/twitch/TwitchAccount.cpp +++ b/src/providers/twitch/TwitchAccount.cpp @@ -4,6 +4,7 @@ #include "Application.hpp" #include "common/NetworkRequest.hpp" +#include "common/Outcome.hpp" #include "debug/Log.hpp" #include "providers/twitch/PartialTwitchUser.hpp" #include "providers/twitch/TwitchCommon.hpp" @@ -14,32 +15,32 @@ namespace chatterino { namespace { -EmoteName cleanUpCode(const EmoteName &dirtyEmoteCode) -{ - auto cleanCode = dirtyEmoteCode.string; - cleanCode.detach(); + EmoteName cleanUpCode(const EmoteName &dirtyEmoteCode) + { + auto cleanCode = dirtyEmoteCode.string; + cleanCode.detach(); - static QMap emoteNameReplacements{ - {"[oO](_|\\.)[oO]", "O_o"}, {"\\>\\;\\(", ">("}, - {"\\<\\;3", "<3"}, {"\\:-?(o|O)", ":O"}, - {"\\:-?(p|P)", ":P"}, {"\\:-?[\\\\/]", ":/"}, - {"\\:-?[z|Z|\\|]", ":Z"}, {"\\:-?\\(", ":("}, - {"\\:-?\\)", ":)"}, {"\\:-?D", ":D"}, - {"\\;-?(p|P)", ";P"}, {"\\;-?\\)", ";)"}, - {"R-?\\)", "R)"}, {"B-?\\)", "B)"}, - }; + static QMap emoteNameReplacements{ + {"[oO](_|\\.)[oO]", "O_o"}, {"\\>\\;\\(", ">("}, + {"\\<\\;3", "<3"}, {"\\:-?(o|O)", ":O"}, + {"\\:-?(p|P)", ":P"}, {"\\:-?[\\\\/]", ":/"}, + {"\\:-?[z|Z|\\|]", ":Z"}, {"\\:-?\\(", ":("}, + {"\\:-?\\)", ":)"}, {"\\:-?D", ":D"}, + {"\\;-?(p|P)", ";P"}, {"\\;-?\\)", ";)"}, + {"R-?\\)", "R)"}, {"B-?\\)", "B)"}, + }; - auto it = emoteNameReplacements.find(dirtyEmoteCode.string); - if (it != emoteNameReplacements.end()) { - cleanCode = it.value(); + auto it = emoteNameReplacements.find(dirtyEmoteCode.string); + if (it != emoteNameReplacements.end()) { + cleanCode = it.value(); + } + + cleanCode.replace("<", "<"); + cleanCode.replace(">", ">"); + + return {cleanCode}; } - cleanCode.replace("<", "<"); - cleanCode.replace(">", ">"); - - return {cleanCode}; -} - } // namespace TwitchAccount::TwitchAccount(const QString &username, const QString &oauthToken, @@ -416,7 +417,7 @@ void TwitchAccount::loadEmotes() } AccessGuard -TwitchAccount::accessEmotes() const + TwitchAccount::accessEmotes() const { return this->emotes_.accessConst(); } diff --git a/src/providers/twitch/TwitchAccount.hpp b/src/providers/twitch/TwitchAccount.hpp index c59ca9d38..1ea718b73 100644 --- a/src/providers/twitch/TwitchAccount.hpp +++ b/src/providers/twitch/TwitchAccount.hpp @@ -1,5 +1,6 @@ #pragma once +#include "common/Aliases.hpp" #include "common/Atomic.hpp" #include "common/UniqueAccess.hpp" #include "controllers/accounts/Account.hpp" diff --git a/src/providers/twitch/TwitchAccountManager.cpp b/src/providers/twitch/TwitchAccountManager.cpp index 18210c2fd..097aa89ea 100644 --- a/src/providers/twitch/TwitchAccountManager.cpp +++ b/src/providers/twitch/TwitchAccountManager.cpp @@ -2,6 +2,7 @@ #include "common/Common.hpp" #include "debug/Log.hpp" +#include "providers/twitch/TwitchAccount.hpp" #include "providers/twitch/TwitchCommon.hpp" namespace chatterino { diff --git a/src/providers/twitch/TwitchAccountManager.hpp b/src/providers/twitch/TwitchAccountManager.hpp index 32961f092..7c79ea249 100644 --- a/src/providers/twitch/TwitchAccountManager.hpp +++ b/src/providers/twitch/TwitchAccountManager.hpp @@ -17,6 +17,7 @@ namespace chatterino { +class TwitchAccount; class AccountController; class TwitchAccountManager diff --git a/src/providers/twitch/TwitchApi.cpp b/src/providers/twitch/TwitchApi.cpp index dc0329b69..33e982191 100644 --- a/src/providers/twitch/TwitchApi.cpp +++ b/src/providers/twitch/TwitchApi.cpp @@ -1,5 +1,6 @@ #include "providers/twitch/TwitchApi.hpp" +#include "common/Common.hpp" #include "common/NetworkRequest.hpp" #include "debug/Log.hpp" #include "providers/twitch/TwitchCommon.hpp" diff --git a/src/providers/twitch/TwitchApi.hpp b/src/providers/twitch/TwitchApi.hpp index 67b26c1ca..fd7ad599c 100644 --- a/src/providers/twitch/TwitchApi.hpp +++ b/src/providers/twitch/TwitchApi.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include namespace chatterino { diff --git a/src/providers/twitch/TwitchBadges.cpp b/src/providers/twitch/TwitchBadges.cpp index fd9fe7461..0e515d627 100644 --- a/src/providers/twitch/TwitchBadges.cpp +++ b/src/providers/twitch/TwitchBadges.cpp @@ -4,19 +4,14 @@ #include #include #include + #include "common/NetworkRequest.hpp" +#include "common/Outcome.hpp" +#include "debug/Log.hpp" +#include "messages/Emote.hpp" namespace chatterino { -TwitchBadges::TwitchBadges() -{ -} - -void TwitchBadges::initialize(Settings &settings, Paths &paths) -{ - this->loadTwitchBadges(); -} - void TwitchBadges::loadTwitchBadges() { static QString url( @@ -26,32 +21,34 @@ void TwitchBadges::loadTwitchBadges() req.setCaller(QThread::currentThread()); req.onSuccess([this](auto result) -> Outcome { auto root = result.parseJson(); - QJsonObject sets = root.value("badge_sets").toObject(); + auto badgeSets = this->badgeSets_.access(); - for (QJsonObject::iterator it = sets.begin(); it != sets.end(); ++it) { - QJsonObject versions = - it.value().toObject().value("versions").toObject(); + auto jsonSets = root.value("badge_sets").toObject(); + for (auto sIt = jsonSets.begin(); sIt != jsonSets.end(); ++sIt) { + auto key = sIt.key(); + auto versions = sIt.value().toObject().value("versions").toObject(); + + for (auto vIt = versions.begin(); vIt != versions.end(); ++vIt) { + auto versionObj = vIt.value().toObject(); - for (auto versionIt = std::begin(versions); - versionIt != std::end(versions); ++versionIt) { auto emote = Emote{ {""}, ImageSet{ - Image::fromUrl({root.value("image_url_1x").toString()}, - 1), - Image::fromUrl({root.value("image_url_2x").toString()}, - 0.5), - Image::fromUrl({root.value("image_url_4x").toString()}, - 0.25), + Image::fromUrl( + {versionObj.value("image_url_1x").toString()}, 1), + Image::fromUrl( + {versionObj.value("image_url_2x").toString()}, .5), + Image::fromUrl( + {versionObj.value("image_url_4x").toString()}, .25), }, Tooltip{root.value("description").toString()}, Url{root.value("clickURL").toString()}}; // "title" // "clickAction" - QJsonObject versionObj = versionIt.value().toObject(); - this->badges.emplace(versionIt.key(), - std::make_shared(emote)); + log("{} {}", key, vIt.key()); + + (*badgeSets)[key][vIt.key()] = std::make_shared(emote); } } @@ -61,4 +58,18 @@ void TwitchBadges::loadTwitchBadges() req.execute(); } +boost::optional TwitchBadges::badge(const QString &set, + const QString &version) const +{ + auto badgeSets = this->badgeSets_.access(); + auto it = badgeSets->find(set); + if (it != badgeSets->end()) { + auto it2 = it->second.find(version); + if (it2 != it->second.end()) { + return it2->second; + } + } + return boost::none; +} + } // namespace chatterino diff --git a/src/providers/twitch/TwitchBadges.hpp b/src/providers/twitch/TwitchBadges.hpp index a228365da..d9023855d 100644 --- a/src/providers/twitch/TwitchBadges.hpp +++ b/src/providers/twitch/TwitchBadges.hpp @@ -1,26 +1,32 @@ #pragma once #include -#include +#include #include + +#include "common/UniqueAccess.hpp" #include "util/QStringHash.hpp" namespace chatterino { +struct Emote; +using EmotePtr = std::shared_ptr; + class Settings; class Paths; class TwitchBadges { public: - TwitchBadges(); - - void initialize(Settings &settings, Paths &paths); - -private: void loadTwitchBadges(); - std::unordered_map badges; + boost::optional badge(const QString &set, + const QString &version) const; + +private: + UniqueAccess< + std::unordered_map>> + badgeSets_; // "bits": { "100": ... "500": ... }; } // namespace chatterino diff --git a/src/providers/twitch/TwitchChannel.cpp b/src/providers/twitch/TwitchChannel.cpp index 7e0a1efc1..d6c55d224 100644 --- a/src/providers/twitch/TwitchChannel.cpp +++ b/src/providers/twitch/TwitchChannel.cpp @@ -1,5 +1,6 @@ #include "providers/twitch/TwitchChannel.hpp" +#include "Application.hpp" #include "common/Common.hpp" #include "common/NetworkRequest.hpp" #include "controllers/accounts/AccountController.hpp" @@ -24,66 +25,87 @@ namespace chatterino { namespace { -auto parseRecentMessages(const QJsonObject &jsonRoot, TwitchChannel &channel) -{ - QJsonArray jsonMessages = jsonRoot.value("messages").toArray(); - std::vector messages; + auto parseRecentMessages(const QJsonObject &jsonRoot, + TwitchChannel &channel) + { + QJsonArray jsonMessages = jsonRoot.value("messages").toArray(); + std::vector messages; - if (jsonMessages.empty()) return messages; + if (jsonMessages.empty()) return messages; - for (const auto jsonMessage : jsonMessages) { - auto content = jsonMessage.toString().toUtf8(); - // passing nullptr as the channel makes the message invalid but we don't - // check for that anyways - auto message = Communi::IrcMessage::fromData(content, nullptr); - auto privMsg = dynamic_cast(message); - assert(privMsg); + for (const auto jsonMessage : jsonMessages) { + auto content = jsonMessage.toString().toUtf8(); + // passing nullptr as the channel makes the message invalid but we + // don't check for that anyways + auto message = Communi::IrcMessage::fromData(content, nullptr); + auto privMsg = dynamic_cast(message); + assert(privMsg); - MessageParseArgs args; - TwitchMessageBuilder builder(&channel, privMsg, args); - if (!builder.isIgnored()) { - messages.push_back(builder.build()); + MessageParseArgs args; + TwitchMessageBuilder builder(&channel, privMsg, args); + if (!builder.isIgnored()) { + messages.push_back(builder.build()); + } } - } - return messages; -} + return messages; + } + std::pair parseChatters(const QJsonObject &jsonRoot) + { + static QStringList categories = {"moderators", "staff", "admins", + "global_mods", "viewers"}; + + auto usernames = UsernameSet(); + + // parse json + QJsonObject jsonCategories = jsonRoot.value("chatters").toObject(); + + for (const auto &category : categories) { + for (auto jsonCategory : jsonCategories.value(category).toArray()) { + usernames.insert(jsonCategory.toString()); + } + } + + return {Success, std::move(usernames)}; + } } // namespace -TwitchChannel::TwitchChannel(const QString &name) +TwitchChannel::TwitchChannel(const QString &name, + TwitchBadges &globalTwitchBadges, BttvEmotes &bttv, + FfzEmotes &ffz) : Channel(name, Channel::Type::Twitch) - , bttvEmotes_(std::make_shared()) - , ffzEmotes_(std::make_shared()) , subscriptionUrl_("https://www.twitch.tv/subs/" + name) , channelUrl_("https://twitch.tv/" + name) , popoutPlayerUrl_("https://player.twitch.tv/?channel=" + name) + , globalTwitchBadges_(globalTwitchBadges) + , globalBttv_(bttv) + , globalFfz_(ffz) + , bttvEmotes_(std::make_shared()) + , ffzEmotes_(std::make_shared()) , mod_(false) { log("[TwitchChannel:{}] Opened", name); - // this->refreshChannelEmotes(); - // this->refreshViewerList(); - this->managedConnect(getApp()->accounts->twitch.currentUserChanged, [=] { this->setMod(false); }); // pubsub - this->userStateChanged.connect([=] { this->refreshPubsub(); }); this->managedConnect(getApp()->accounts->twitch.currentUserChanged, [=] { this->refreshPubsub(); }); this->refreshPubsub(); + this->userStateChanged.connect([this] { this->refreshPubsub(); }); // room id loaded -> refresh live status this->roomIdChanged.connect([this]() { this->refreshPubsub(); this->refreshLiveStatus(); - this->loadBadges(); - this->loadCheerEmotes(); + this->refreshBadges(); + this->refreshCheerEmotes(); }); // timers QObject::connect(&this->chattersListTimer_, &QTimer::timeout, - [=] { this->refreshViewerList(); }); + [=] { this->refreshChatters(); }); this->chattersListTimer_.start(5 * 60 * 1000); QObject::connect(&this->liveStatusTimer_, &QTimer::timeout, @@ -97,11 +119,17 @@ TwitchChannel::TwitchChannel(const QString &name) // debugging #if 0 for (int i = 0; i < 1000; i++) { - this->addMessage(makeSystemMessage("asdf")); + this->addMessage(makeSystemMessage("asef")); } #endif } +void TwitchChannel::initialize() +{ + this->refreshChatters(); + this->refreshChannelEmotes(); +} + bool TwitchChannel::isEmpty() const { return this->getName().isEmpty(); @@ -192,9 +220,7 @@ bool TwitchChannel::isBroadcaster() const void TwitchChannel::addRecentChatter(const MessagePtr &message) { - assert(!message->loginName.isEmpty()); - - this->completionModel.addUser(message->displayName); + this->chatters_.access()->insert(message->displayName); } void TwitchChannel::addJoinedUser(const QString &user) @@ -284,11 +310,31 @@ bool TwitchChannel::isLive() const } AccessGuard -TwitchChannel::accessStreamStatus() const + TwitchChannel::accessStreamStatus() const { return this->streamStatus_.accessConst(); } +AccessGuard TwitchChannel::accessChatters() const +{ + return this->chatters_.accessConst(); +} + +const TwitchBadges &TwitchChannel::globalTwitchBadges() const +{ + return this->globalTwitchBadges_; +} + +const BttvEmotes &TwitchChannel::globalBttv() const +{ + return this->globalBttv_; +} + +const FfzEmotes &TwitchChannel::globalFfz() const +{ + return this->globalFfz_; +} + boost::optional TwitchChannel::bttvEmote(const EmoteName &name) const { auto emotes = this->bttvEmotes_.get(); @@ -300,7 +346,7 @@ boost::optional TwitchChannel::bttvEmote(const EmoteName &name) const boost::optional TwitchChannel::ffzEmote(const EmoteName &name) const { - auto emotes = this->bttvEmotes_.get(); + auto emotes = this->ffzEmotes_.get(); auto it = emotes->find(name); if (it == emotes->end()) return boost::none; @@ -371,7 +417,7 @@ void TwitchChannel::refreshLiveStatus() //>>>>>>> 9bfbdefd2f0972a738230d5b95a009f73b1dd933 request.onSuccess( - [this, weak = this->weak_from_this()](auto result) -> Outcome { + [this, weak = weakOf(this)](auto result) -> Outcome { ChannelPtr shared = weak.lock(); if (!shared) return Failure; @@ -494,7 +540,7 @@ void TwitchChannel::refreshPubsub() account); } -void TwitchChannel::refreshViewerList() +void TwitchChannel::refreshChatters() { // setting? const auto streamStatus = this->accessStreamStatus(); @@ -512,36 +558,23 @@ void TwitchChannel::refreshViewerList() request.setCaller(QThread::currentThread()); request.onSuccess( - [this, weak = this->weak_from_this()](auto result) -> Outcome { + [this, weak = weakOf(this)](auto result) -> Outcome { // channel still exists? auto shared = weak.lock(); if (!shared) return Failure; - return this->parseViewerList(result.parseJson()); + auto pair = parseChatters(result.parseJson()); + if (pair.first) { + *this->chatters_.access() = std::move(pair.second); + } + + return pair.first; }); request.execute(); } -Outcome TwitchChannel::parseViewerList(const QJsonObject &jsonRoot) -{ - static QStringList categories = {"moderators", "staff", "admins", - "global_mods", "viewers"}; - - // parse json - QJsonObject jsonCategories = jsonRoot.value("chatters").toObject(); - - for (const auto &category : categories) { - for (const auto jsonCategory : - jsonCategories.value(category).toArray()) { - this->completionModel.addUser(jsonCategory.toString()); - } - } - - return Success; -} - -void TwitchChannel::loadBadges() +void TwitchChannel::refreshBadges() { auto url = Url{"https://badges.twitch.tv/v1/badges/channels/" + this->roomId() + "/display?language=en"}; @@ -586,7 +619,7 @@ void TwitchChannel::loadBadges() req.execute(); } -void TwitchChannel::loadCheerEmotes() +void TwitchChannel::refreshCheerEmotes() { /*auto url = Url{"https://api.twitch.tv/kraken/bits/actions?channel_id=" + this->getRoomId()}; @@ -650,7 +683,7 @@ void TwitchChannel::loadCheerEmotes() */ } -boost::optional TwitchChannel::getTwitchBadge( +boost::optional TwitchChannel::twitchBadge( const QString &set, const QString &version) const { auto badgeSets = this->badgeSets_.access(); diff --git a/src/providers/twitch/TwitchChannel.hpp b/src/providers/twitch/TwitchChannel.hpp index 8b2edeab4..82ae58d40 100644 --- a/src/providers/twitch/TwitchChannel.hpp +++ b/src/providers/twitch/TwitchChannel.hpp @@ -1,22 +1,32 @@ #pragma once -#include - +#include "common/Aliases.hpp" #include "common/Atomic.hpp" #include "common/Channel.hpp" -#include "common/Common.hpp" +#include "common/Outcome.hpp" #include "common/UniqueAccess.hpp" -#include "messages/Emote.hpp" -#include "singletons/Emotes.hpp" -#include "util/ConcurrentMap.hpp" - -#include +#include "common/UsernameSet.hpp" +#include "providers/twitch/TwitchEmotes.hpp" +#include +#include +#include +#include +#include #include +#include #include namespace chatterino { +struct Emote; +using EmotePtr = std::shared_ptr; +class EmoteMap; + +class TwitchBadges; +class FfzEmotes; +class BttvEmotes; + class TwitchServer; class TwitchChannel final : public Channel, pajlada::Signals::SignalHolder @@ -32,11 +42,6 @@ public: QString streamType; }; - struct UserState { - bool mod; - bool broadcaster; - }; - struct RoomModes { bool submode = false; bool r9k = false; @@ -46,92 +51,94 @@ public: QString broadcasterLang; }; - void refreshChannelEmotes(); + void initialize(); // Channel methods virtual bool isEmpty() const override; virtual bool canSendMessage() const override; virtual void sendMessage(const QString &message) override; - - // Auto completion - void addRecentChatter(const MessagePtr &message) final; - void addJoinedUser(const QString &user); - void addPartedUser(const QString &user); - - // Twitch data - bool isLive() const; virtual bool isMod() const override; - void setMod(bool value); virtual bool isBroadcaster() const override; + // Data + const QString &subscriptionUrl(); + const QString &channelUrl(); + const QString &popoutPlayerUrl(); + bool isLive() const; QString roomId() const; - void setRoomId(const QString &id); AccessGuard accessRoomModes() const; - void setRoomModes(const RoomModes &roomModes_); AccessGuard accessStreamStatus() const; + AccessGuard accessChatters() const; + // Emotes + const TwitchBadges &globalTwitchBadges() const; + const BttvEmotes &globalBttv() const; + const FfzEmotes &globalFfz() const; boost::optional bttvEmote(const EmoteName &name) const; boost::optional ffzEmote(const EmoteName &name) const; std::shared_ptr bttvEmotes() const; std::shared_ptr ffzEmotes() const; - const QString &subscriptionUrl(); - const QString &channelUrl(); - const QString &popoutPlayerUrl(); - boost::optional getTwitchBadge(const QString &set, - const QString &version) const; + void refreshChannelEmotes(); + + // Badges + boost::optional twitchBadge(const QString &set, + const QString &version) const; // Signals pajlada::Signals::NoArgSignal roomIdChanged; - pajlada::Signals::NoArgSignal liveStatusChanged; pajlada::Signals::NoArgSignal userStateChanged; + pajlada::Signals::NoArgSignal liveStatusChanged; pajlada::Signals::NoArgSignal roomModesChanged; +protected: + void addRecentChatter(const MessagePtr &message) override; + private: struct NameOptions { QString displayName; QString localizedName; }; - struct CheerEmote { - // a Cheermote indicates one tier - QColor color; - int minBits; - - EmotePtr animatedEmote; - EmotePtr staticEmote; - }; - - struct CheerEmoteSet { - QRegularExpression regex; - std::vector cheerEmotes; - }; - - explicit TwitchChannel(const QString &channelName); + explicit TwitchChannel(const QString &channelName, + TwitchBadges &globalTwitchBadges, + BttvEmotes &globalBttv, FfzEmotes &globalFfz); // Methods void refreshLiveStatus(); Outcome parseLiveStatus(const rapidjson::Document &document); void refreshPubsub(); - void refreshViewerList(); - Outcome parseViewerList(const QJsonObject &jsonRoot); + void refreshChatters(); + void refreshBadges(); + void refreshCheerEmotes(); void loadRecentMessages(); + void addJoinedUser(const QString &user); + void addPartedUser(const QString &user); void setLive(bool newLiveStatus); + void setMod(bool value); + void setRoomId(const QString &id); + void setRoomModes(const RoomModes &roomModes_); - void loadBadges(); - void loadCheerEmotes(); - - // Twitch data - UniqueAccess streamStatus_; - UniqueAccess userState_; - UniqueAccess roomModes_; - - Atomic> bttvEmotes_; - Atomic> ffzEmotes_; + // Data const QString subscriptionUrl_; const QString channelUrl_; const QString popoutPlayerUrl_; + UniqueAccess streamStatus_; + UniqueAccess roomModes_; + UniqueAccess chatters_; // maps 2 char prefix to set of names + + // Emotes + TwitchBadges &globalTwitchBadges_; + BttvEmotes &globalBttv_; + FfzEmotes &globalFfz_; + Atomic> bttvEmotes_; + Atomic> ffzEmotes_; + + // Badges + UniqueAccess>> + badgeSets_; // "subscribers": { "0": ... "3": ... "6": ... + UniqueAccess> cheerEmoteSets_; bool mod_ = false; UniqueAccess roomID_; @@ -141,10 +148,6 @@ private: UniqueAccess partedUsers_; bool partedUsersMergeQueued_ = false; - // "subscribers": { "0": ... "3": ... "6": ... - UniqueAccess>> badgeSets_; - UniqueAccess> cheerEmoteSets_; - // -- QByteArray messageSuffix_; QString lastSentMessage_; @@ -153,6 +156,8 @@ private: QTimer chattersListTimer_; friend class TwitchServer; + friend class TwitchMessageBuilder; + friend class IrcMessageHandler; }; } // namespace chatterino diff --git a/src/providers/twitch/TwitchEmotes.cpp b/src/providers/twitch/TwitchEmotes.cpp index 08c28286f..ce63d6864 100644 --- a/src/providers/twitch/TwitchEmotes.cpp +++ b/src/providers/twitch/TwitchEmotes.cpp @@ -3,6 +3,7 @@ #include "common/NetworkRequest.hpp" #include "debug/Benchmark.hpp" #include "debug/Log.hpp" +#include "messages/Emote.hpp" #include "messages/Image.hpp" #include "util/RapidjsonHelpers.hpp" diff --git a/src/providers/twitch/TwitchEmotes.hpp b/src/providers/twitch/TwitchEmotes.hpp index 69cf65e5e..1c1b0b216 100644 --- a/src/providers/twitch/TwitchEmotes.hpp +++ b/src/providers/twitch/TwitchEmotes.hpp @@ -1,19 +1,33 @@ #pragma once +#include +#include #include #include +#include "common/Aliases.hpp" #include "common/UniqueAccess.hpp" -#include "messages/Emote.hpp" -#include "providers/twitch/EmoteValue.hpp" -#include "providers/twitch/TwitchAccount.hpp" #include "providers/twitch/TwitchEmotes.hpp" -#include "util/ConcurrentMap.hpp" #define TWITCH_EMOTE_TEMPLATE \ "https://static-cdn.jtvnw.net/emoticons/v1/{id}/{scale}" namespace chatterino { +struct Emote; +using EmotePtr = std::shared_ptr; + +struct CheerEmote { + QColor color; + int minBits; + + EmotePtr animatedEmote; + EmotePtr staticEmote; +}; + +struct CheerEmoteSet { + QRegularExpression regex; + std::vector cheerEmotes; +}; class TwitchEmotes { diff --git a/src/providers/twitch/TwitchMessageBuilder.cpp b/src/providers/twitch/TwitchMessageBuilder.cpp index d1deaf719..f6feb61d9 100644 --- a/src/providers/twitch/TwitchMessageBuilder.cpp +++ b/src/providers/twitch/TwitchMessageBuilder.cpp @@ -5,6 +5,8 @@ #include "controllers/highlights/HighlightController.hpp" #include "controllers/ignores/IgnoreController.hpp" #include "debug/Log.hpp" +#include "messages/Message.hpp" +#include "providers/twitch/TwitchBadges.hpp" #include "providers/twitch/TwitchChannel.hpp" #include "singletons/Emotes.hpp" #include "singletons/Resources.hpp" @@ -12,6 +14,7 @@ #include "singletons/Theme.hpp" #include "singletons/WindowManager.hpp" #include "util/IrcHelpers.hpp" +#include "widgets/Window.hpp" #include #include @@ -61,7 +64,7 @@ bool TwitchMessageBuilder::isIgnored() const } } - if (app->settings->enableTwitchIgnoredUsers && this->tags.contains("user-id")) { + if (getSettings()->enableTwitchIgnoredUsers && this->tags.contains("user-id")) { auto sourceUserID = this->tags.value("user-id").toString(); for (const auto &user : app->accounts->twitch.getCurrent()->getIgnores()) { @@ -77,8 +80,6 @@ bool TwitchMessageBuilder::isIgnored() const MessagePtr TwitchMessageBuilder::build() { - auto app = getApp(); - // PARSING this->parseUsername(); @@ -86,13 +87,6 @@ MessagePtr TwitchMessageBuilder::build() this->senderIsBroadcaster = true; } - //#ifdef XD - // if (this->originalMessage.length() > 100) { - // this->message->flags.has(MessageFlag::Collapsed); - // this->emplace(getApp()->resources->badgeCollapsed, - // MessageElementFlag::Collapsed); - // } - //#endif this->message().flags.has(MessageFlag::Collapsed); // PARSING @@ -329,11 +323,7 @@ void TwitchMessageBuilder::addWords( // split words for (auto &variant : getApp()->emotes->emojis.parse(word)) { - boost::apply_visitor(/*overloaded{[&](EmotePtr arg) { - this->addTextOrEmoji(arg); }, - [&](const QString &arg) { - this->addTextOrEmoji(arg); }}*/ - [&](auto &&arg) { this->addTextOrEmoji(arg); }, variant); + boost::apply_visitor([&](auto &&arg) { this->addTextOrEmoji(arg); }, variant); } for (int j = 0; j < word.size(); j++) { @@ -410,7 +400,7 @@ void TwitchMessageBuilder::addTextOrEmoji(const QString &string_) } // if (!linkString.isEmpty()) { - // if (getApp()->settings->lowercaseLink) { + // if (getSettings()->lowercaseLink) { // QRegularExpression httpRegex("\\bhttps?://", // QRegularExpression::CaseInsensitiveOption); QRegularExpression // ftpRegex("\\bftps?://", @@ -565,14 +555,14 @@ void TwitchMessageBuilder::appendUsername() // IrcManager::getInstance().getUser().getUserName(); } else if (this->args.isReceivedWhisper) { // Sender username - this->emplace(usernameText, MessageElementFlag::Text, this->usernameColor_, + this->emplace(usernameText, MessageElementFlag::Username, this->usernameColor_, FontStyle::ChatMediumBold) ->setLink({Link::UserInfo, this->userName}); auto currentUser = app->accounts->twitch.getCurrent(); // Separator - this->emplace("->", MessageElementFlag::Text, + this->emplace("->", MessageElementFlag::Username, app->themes->messages.textColors.system, FontStyle::ChatMedium); QColor selfColor = currentUser->color(); @@ -581,14 +571,14 @@ void TwitchMessageBuilder::appendUsername() } // Your own username - this->emplace(currentUser->getUserName() + ":", MessageElementFlag::Text, + this->emplace(currentUser->getUserName() + ":", MessageElementFlag::Username, selfColor, FontStyle::ChatMediumBold); } else { if (!this->action_) { usernameText += ":"; } - this->emplace(usernameText, MessageElementFlag::Text, this->usernameColor_, + this->emplace(usernameText, MessageElementFlag::Username, this->usernameColor_, FontStyle::ChatMediumBold) ->setLink({Link::UserInfo, this->userName}); } @@ -613,8 +603,8 @@ void TwitchMessageBuilder::parseHighlights(bool isPastMsg) // update the media player url if necessary QUrl highlightSoundUrl; - if (app->settings->customHighlightSound) { - highlightSoundUrl = QUrl::fromLocalFile(app->settings->pathHighlightSound.getValue()); + if (getSettings()->customHighlightSound) { + highlightSoundUrl = QUrl::fromLocalFile(getSettings()->pathHighlightSound.getValue()); } else { highlightSoundUrl = QUrl("qrc:/sounds/ping2.wav"); } @@ -630,9 +620,9 @@ void TwitchMessageBuilder::parseHighlights(bool isPastMsg) std::vector activeHighlights = app->highlights->phrases.getVector(); std::vector userHighlights = app->highlights->highlightedUsers.getVector(); - if (app->settings->enableHighlightsSelf && currentUsername.size() > 0) { - HighlightPhrase selfHighlight(currentUsername, app->settings->enableHighlightTaskbar, - app->settings->enableHighlightSound, false); + if (getSettings()->enableHighlightsSelf && currentUsername.size() > 0) { + HighlightPhrase selfHighlight(currentUsername, getSettings()->enableHighlightTaskbar, + getSettings()->enableHighlightSound, false); activeHighlights.emplace_back(std::move(selfHighlight)); } @@ -689,7 +679,7 @@ void TwitchMessageBuilder::parseHighlights(bool isPastMsg) this->message().flags.set(MessageFlag::Highlighted, doHighlight); if (!isPastMsg) { - if (playSound && (!hasFocus || app->settings->highlightAlwaysPlaySound)) { + if (playSound && (!hasFocus || getSettings()->highlightAlwaysPlaySound)) { player->play(); } @@ -748,13 +738,13 @@ Outcome TwitchMessageBuilder::tryAppendEmote(const EmoteName &name) auto flags = MessageElementFlags(); auto emote = boost::optional{}; - if ((emote = getApp()->emotes->bttv.global(name))) { + if ((emote = this->twitchChannel->globalBttv().emote(name))) { flags = MessageElementFlag::BttvEmote; - } else if (twitchChannel && (emote = this->twitchChannel->bttvEmote(name))) { + } else if ((emote = this->twitchChannel->bttvEmote(name))) { flags = MessageElementFlag::BttvEmote; - } else if ((emote = getApp()->emotes->ffz.global(name))) { + } else if ((emote = this->twitchChannel->globalFfz().emote(name))) { flags = MessageElementFlag::FfzEmote; - } else if (twitchChannel && (emote = this->twitchChannel->ffzEmote(name))) { + } else if ((emote = this->twitchChannel->ffzEmote(name))) { flags = MessageElementFlag::FfzEmote; } @@ -767,40 +757,23 @@ Outcome TwitchMessageBuilder::tryAppendEmote(const EmoteName &name) } // fourtf: this is ugly -// maybe put the individual badges into a map instead of this -// mess void TwitchMessageBuilder::appendTwitchBadges() { auto app = getApp(); auto iterator = this->tags.find("badges"); - - if (iterator == this->tags.end()) { - // No badges in this message + if (iterator == this->tags.end()) return; - } - - QStringList badges = iterator.value().toString().split(','); - - for (QString badge : badges) { - if (badge.isEmpty()) { - continue; - } + for (QString badge : iterator.value().toString().split(',')) { if (badge.startsWith("bits/")) { - // if (!app->resources->dynamicBadgesLoaded) { - // // Do nothing - // continue; - // } - QString cheerAmount = badge.mid(5); QString tooltip = QString("Twitch cheer ") + cheerAmount; // Try to fetch channel-specific bit badge try { if (twitchChannel) - if (const auto &badge = - this->twitchChannel->getTwitchBadge("bits", cheerAmount)) { + if (const auto &badge = this->twitchChannel->twitchBadge("bits", cheerAmount)) { this->emplace(badge.get(), MessageElementFlag::BadgeVanity) ->setTooltip(tooltip); continue; @@ -810,16 +783,10 @@ void TwitchMessageBuilder::appendTwitchBadges() } // Use default bit badge - // try { - // const auto &badge = - // app->resources->badgeSets.at("bits").versions.at(cheerAmount); - // this->emplace(badge.badgeImage1x, - // MessageElementFlag::BadgeVanity) - // ->setTooltip(tooltip); - //} catch (const std::out_of_range &) { - // Log("No default bit badge for version {} found", cheerAmount); - // continue; - //} + if (auto badge = this->twitchChannel->globalTwitchBadges().badge("bits", cheerAmount)) { + this->emplace(badge.get(), MessageElementFlag::BadgeVanity) + ->setTooltip(tooltip); + } } else if (badge == "staff/1") { this->emplace(Image::fromPixmap(app->resources->twitch.staff), MessageElementFlag::BadgeGlobalAuthority) @@ -865,80 +832,26 @@ void TwitchMessageBuilder::appendTwitchBadges() } break; } } else if (badge.startsWith("subscriber/")) { - // if (channelResources.loaded == false) { - // // qDebug() << "Channel resources are not loaded, - // can't add the subscriber - // // badge"; - // continue; - // } + if (auto badgeEmote = this->twitchChannel->twitchBadge("subscriber", badge.mid(11))) { + this->emplace(badgeEmote.get(), MessageElementFlag::BadgeSubscription) + ->setTooltip((*badgeEmote)->tooltip.string); + continue; + } - // auto badgeSetIt = channelResources.badgeSets.find("subscriber"); - // if (badgeSetIt == channelResources.badgeSets.end()) { - // // Fall back to default badge - // this->emplace(app->resources->badgeSubscriber, - // MessageElementFlag::BadgeSubscription) - // ->setTooltip("Twitch Subscriber"); - // continue; - //} - - // const auto &badgeSet = badgeSetIt->second; - - // std::string versionKey = badge.mid(11).toStdString(); - - // auto badgeVersionIt = badgeSet.versions.find(versionKey); - - // if (badgeVersionIt == badgeSet.versions.end()) { - // // Fall back to default badge - // this->emplace(app->resources->badgeSubscriber, - // MessageElementFlag::BadgeSubscription) - // ->setTooltip("Twitch Subscriber"); - // continue; - //} - - // auto &badgeVersion = badgeVersionIt->second; - - // this->emplace(badgeVersion.badgeImage1x, - // MessageElementFlag::BadgeSubscription) - // ->setTooltip("Twitch " + - // QString::fromStdString(badgeVersion.title)); + // use default subscriber badge if custom one not found + this->emplace(Image::fromPixmap(app->resources->twitch.subscriber, 0.25), + MessageElementFlag::BadgeSubscription) + ->setTooltip("Twitch Subscriber"); } else { - // if (!app->resources->dynamicBadgesLoaded) { - // // Do nothing - // continue; - //} + auto splits = badge.split('/'); + if (splits.size() != 2) + continue; - // QStringList parts = badge.split('/'); - - // if (parts.length() != 2) { - // qDebug() << "Bad number of parts: " << parts.length() << " in - // " << parts; continue; - //} - - // MessageElementFlags badgeType = - // MessageElementFlag::BadgeVanity; - - // std::string badgeSetKey = parts[0].toStdString(); - // std::string versionKey = parts[1].toStdString(); - - // try { - // auto &badgeSet = app->resources->badgeSets.at(badgeSetKey); - - // try { - // auto &badgeVersion = badgeSet.versions.at(versionKey); - - // this->emplace(badgeVersion.badgeImage1x, - // badgeType) - // ->setTooltip("Twitch " + - // QString::fromStdString(badgeVersion.title)); - // } catch (const std::exception &e) { - // qDebug() << "Exception caught:" << e.what() - // << "when trying to fetch badge version " << - // versionKey.c_str(); - // } - //} catch (const std::exception &e) { - // qDebug() << "No badge set with key" << badgeSetKey.c_str() - // << ". Exception: " << e.what(); - //} + if (auto badgeEmote = this->twitchChannel->twitchBadge(splits[0], splits[1])) { + this->emplace(badgeEmote.get(), MessageElementFlag::BadgeVanity) + ->setTooltip((*badgeEmote)->tooltip.string); + continue; + } } } } diff --git a/src/providers/twitch/TwitchMessageBuilder.hpp b/src/providers/twitch/TwitchMessageBuilder.hpp index 3195749c4..a064baa32 100644 --- a/src/providers/twitch/TwitchMessageBuilder.hpp +++ b/src/providers/twitch/TwitchMessageBuilder.hpp @@ -1,15 +1,18 @@ #pragma once +#include "common/Aliases.hpp" +#include "common/Outcome.hpp" #include "messages/MessageBuilder.hpp" -#include "singletons/Emotes.hpp" #include - #include #include namespace chatterino { +struct Emote; +using EmotePtr = std::shared_ptr; + class Channel; class TwitchChannel; diff --git a/src/providers/twitch/TwitchParseCheerEmotes.cpp b/src/providers/twitch/TwitchParseCheerEmotes.cpp index 47d6427f8..65eca1fa3 100644 --- a/src/providers/twitch/TwitchParseCheerEmotes.cpp +++ b/src/providers/twitch/TwitchParseCheerEmotes.cpp @@ -8,235 +8,235 @@ namespace chatterino { namespace { -template -inline bool ReadValue(const rapidjson::Value &object, const char *key, - Type &out) -{ - if (!object.HasMember(key)) { - return false; - } - - const auto &value = object[key]; - - if (!value.Is()) { - return false; - } - - out = value.Get(); - - return true; -} - -template <> -inline bool ReadValue(const rapidjson::Value &object, const char *key, - QString &out) -{ - if (!object.HasMember(key)) { - return false; - } - - const auto &value = object[key]; - - if (!value.IsString()) { - return false; - } - - out = value.GetString(); - - return true; -} - -template <> -inline bool ReadValue>(const rapidjson::Value &object, - const char *key, - std::vector &out) -{ - if (!object.HasMember(key)) { - return false; - } - - const auto &value = object[key]; - - if (!value.IsArray()) { - return false; - } - - for (const rapidjson::Value &innerValue : value.GetArray()) { - if (!innerValue.IsString()) { + template + inline bool ReadValue(const rapidjson::Value &object, const char *key, + Type &out) + { + if (!object.HasMember(key)) { return false; } - out.emplace_back(innerValue.GetString()); - } + const auto &value = object[key]; - return true; -} - -// Parse a single cheermote set (or "action") from the twitch api -inline bool ParseSingleCheermoteSet(JSONCheermoteSet &set, - const rapidjson::Value &action) -{ - if (!action.IsObject()) { - return false; - } - - if (!ReadValue(action, "prefix", set.prefix)) { - return false; - } - - if (!ReadValue(action, "scales", set.scales)) { - return false; - } - - if (!ReadValue(action, "backgrounds", set.backgrounds)) { - return false; - } - - if (!ReadValue(action, "states", set.states)) { - return false; - } - - if (!ReadValue(action, "type", set.type)) { - return false; - } - - if (!ReadValue(action, "updated_at", set.updatedAt)) { - return false; - } - - if (!ReadValue(action, "priority", set.priority)) { - return false; - } - - // Tiers - if (!action.HasMember("tiers")) { - return false; - } - - const auto &tiersValue = action["tiers"]; - - if (!tiersValue.IsArray()) { - return false; - } - - for (const rapidjson::Value &tierValue : tiersValue.GetArray()) { - JSONCheermoteSet::CheermoteTier tier; - - if (!tierValue.IsObject()) { + if (!value.Is()) { return false; } - if (!ReadValue(tierValue, "min_bits", tier.minBits)) { + out = value.Get(); + + return true; + } + + template <> + inline bool ReadValue(const rapidjson::Value &object, + const char *key, QString &out) + { + if (!object.HasMember(key)) { return false; } - if (!ReadValue(tierValue, "id", tier.id)) { + const auto &value = object[key]; + + if (!value.IsString()) { return false; } - if (!ReadValue(tierValue, "color", tier.color)) { + out = value.GetString(); + + return true; + } + + template <> + inline bool ReadValue>(const rapidjson::Value &object, + const char *key, + std::vector &out) + { + if (!object.HasMember(key)) { return false; } - // Images - if (!tierValue.HasMember("images")) { + const auto &value = object[key]; + + if (!value.IsArray()) { return false; } - const auto &imagesValue = tierValue["images"]; - - if (!imagesValue.IsObject()) { - return false; - } - - // Read images object - for (const auto &imageBackgroundValue : imagesValue.GetObject()) { - QString background = imageBackgroundValue.name.GetString(); - bool backgroundExists = false; - for (const auto &bg : set.backgrounds) { - if (background == bg) { - backgroundExists = true; - break; - } + for (const rapidjson::Value &innerValue : value.GetArray()) { + if (!innerValue.IsString()) { + return false; } - if (!backgroundExists) { - continue; + out.emplace_back(innerValue.GetString()); + } + + return true; + } + + // Parse a single cheermote set (or "action") from the twitch api + inline bool ParseSingleCheermoteSet(JSONCheermoteSet &set, + const rapidjson::Value &action) + { + if (!action.IsObject()) { + return false; + } + + if (!ReadValue(action, "prefix", set.prefix)) { + return false; + } + + if (!ReadValue(action, "scales", set.scales)) { + return false; + } + + if (!ReadValue(action, "backgrounds", set.backgrounds)) { + return false; + } + + if (!ReadValue(action, "states", set.states)) { + return false; + } + + if (!ReadValue(action, "type", set.type)) { + return false; + } + + if (!ReadValue(action, "updated_at", set.updatedAt)) { + return false; + } + + if (!ReadValue(action, "priority", set.priority)) { + return false; + } + + // Tiers + if (!action.HasMember("tiers")) { + return false; + } + + const auto &tiersValue = action["tiers"]; + + if (!tiersValue.IsArray()) { + return false; + } + + for (const rapidjson::Value &tierValue : tiersValue.GetArray()) { + JSONCheermoteSet::CheermoteTier tier; + + if (!tierValue.IsObject()) { + return false; } - const rapidjson::Value &imageBackgroundStates = - imageBackgroundValue.value; - if (!imageBackgroundStates.IsObject()) { - continue; + if (!ReadValue(tierValue, "min_bits", tier.minBits)) { + return false; } - // Read each key which represents a background - for (const auto &imageBackgroundState : - imageBackgroundStates.GetObject()) { - QString state = imageBackgroundState.name.GetString(); - bool stateExists = false; - for (const auto &_state : set.states) { - if (state == _state) { - stateExists = true; + if (!ReadValue(tierValue, "id", tier.id)) { + return false; + } + + if (!ReadValue(tierValue, "color", tier.color)) { + return false; + } + + // Images + if (!tierValue.HasMember("images")) { + return false; + } + + const auto &imagesValue = tierValue["images"]; + + if (!imagesValue.IsObject()) { + return false; + } + + // Read images object + for (const auto &imageBackgroundValue : imagesValue.GetObject()) { + QString background = imageBackgroundValue.name.GetString(); + bool backgroundExists = false; + for (const auto &bg : set.backgrounds) { + if (background == bg) { + backgroundExists = true; break; } } - if (!stateExists) { + if (!backgroundExists) { continue; } - const rapidjson::Value &imageScalesValue = - imageBackgroundState.value; - if (!imageScalesValue.IsObject()) { + const rapidjson::Value &imageBackgroundStates = + imageBackgroundValue.value; + if (!imageBackgroundStates.IsObject()) { continue; } - // Read each key which represents a scale - for (const auto &imageScaleValue : - imageScalesValue.GetObject()) { - QString scale = imageScaleValue.name.GetString(); - bool scaleExists = false; - for (const auto &_scale : set.scales) { - if (scale == _scale) { - scaleExists = true; + // Read each key which represents a background + for (const auto &imageBackgroundState : + imageBackgroundStates.GetObject()) { + QString state = imageBackgroundState.name.GetString(); + bool stateExists = false; + for (const auto &_state : set.states) { + if (state == _state) { + stateExists = true; break; } } - if (!scaleExists) { + if (!stateExists) { continue; } - const rapidjson::Value &imageScaleURLValue = - imageScaleValue.value; - if (!imageScaleURLValue.IsString()) { + const rapidjson::Value &imageScalesValue = + imageBackgroundState.value; + if (!imageScalesValue.IsObject()) { continue; } - QString url = imageScaleURLValue.GetString(); + // Read each key which represents a scale + for (const auto &imageScaleValue : + imageScalesValue.GetObject()) { + QString scale = imageScaleValue.name.GetString(); + bool scaleExists = false; + for (const auto &_scale : set.scales) { + if (scale == _scale) { + scaleExists = true; + break; + } + } - bool ok = false; - qreal scaleNumber = scale.toFloat(&ok); - if (!ok) { - continue; + if (!scaleExists) { + continue; + } + + const rapidjson::Value &imageScaleURLValue = + imageScaleValue.value; + if (!imageScaleURLValue.IsString()) { + continue; + } + + QString url = imageScaleURLValue.GetString(); + + bool ok = false; + qreal scaleNumber = scale.toFloat(&ok); + if (!ok) { + continue; + } + + qreal chatterinoScale = 1 / scaleNumber; + + auto image = Image::fromUrl({url}, chatterinoScale); + + // TODO(pajlada): Fill in name and tooltip + tier.images[background][state][scale] = image; } - - qreal chatterinoScale = 1 / scaleNumber; - - auto image = Image::fromUrl({url}, chatterinoScale); - - // TODO(pajlada): Fill in name and tooltip - tier.images[background][state][scale] = image; } } + + set.tiers.emplace_back(tier); } - set.tiers.emplace_back(tier); + return true; } - - return true; -} } // namespace // Look through the results of diff --git a/src/providers/twitch/TwitchServer.cpp b/src/providers/twitch/TwitchServer.cpp index 4e3bcddad..d1e03b63b 100644 --- a/src/providers/twitch/TwitchServer.cpp +++ b/src/providers/twitch/TwitchServer.cpp @@ -7,12 +7,12 @@ #include "providers/twitch/IrcMessageHandler.hpp" #include "providers/twitch/PubsubClient.hpp" #include "providers/twitch/TwitchAccount.hpp" +#include "providers/twitch/TwitchChannel.hpp" #include "providers/twitch/TwitchHelpers.hpp" #include "providers/twitch/TwitchMessageBuilder.hpp" #include "util/PostToThread.hpp" #include - #include // using namespace Communi; @@ -39,6 +39,10 @@ void TwitchServer::initialize(Settings &settings, Paths &paths) { getApp()->accounts->twitch.currentUserChanged.connect( [this]() { postToThread([this] { this->connect(); }); }); + + this->twitchBadges.loadTwitchBadges(); + this->bttv.loadEmotes(); + this->ffz.loadEmotes(); } void TwitchServer::initializeConnection(IrcConnection *connection, bool isRead, @@ -79,9 +83,9 @@ void TwitchServer::initializeConnection(IrcConnection *connection, bool isRead, std::shared_ptr TwitchServer::createChannel(const QString &channelName) { - auto channel = - std::shared_ptr(new TwitchChannel(channelName)); - channel->refreshChannelEmotes(); + auto channel = std::shared_ptr(new TwitchChannel( + channelName, this->twitchBadges, this->bttv, this->ffz)); + channel->initialize(); channel->sendMessageSignal.connect( [this, channel = channel.get()](auto &chan, auto &msg, bool &sent) { diff --git a/src/providers/twitch/TwitchServer.hpp b/src/providers/twitch/TwitchServer.hpp index 7e204173f..60026a142 100644 --- a/src/providers/twitch/TwitchServer.hpp +++ b/src/providers/twitch/TwitchServer.hpp @@ -1,10 +1,13 @@ #pragma once #include "common/Atomic.hpp" +#include "common/Channel.hpp" #include "common/Singleton.hpp" +#include "pajlada/signals/signalholder.hpp" +#include "providers/bttv/BttvEmotes.hpp" +#include "providers/ffz/FfzEmotes.hpp" #include "providers/irc/AbstractIrcServer.hpp" -#include "providers/twitch/TwitchAccount.hpp" -#include "providers/twitch/TwitchChannel.hpp" +#include "providers/twitch/TwitchBadges.hpp" #include #include @@ -14,8 +17,8 @@ namespace chatterino { class Settings; class Paths; - class PubSub; +class TwitchChannel; class TwitchServer final : public AbstractIrcServer, public Singleton { @@ -66,6 +69,9 @@ private: std::chrono::steady_clock::time_point lastErrorTimeAmount_; bool singleConnection_ = false; + TwitchBadges twitchBadges; + BttvEmotes bttv; + FfzEmotes ffz; pajlada::Signals::SignalHolder signalHolder_; }; diff --git a/src/providers/twitch/TwitchUser.hpp b/src/providers/twitch/TwitchUser.hpp index 74a9c1252..27539386d 100644 --- a/src/providers/twitch/TwitchUser.hpp +++ b/src/providers/twitch/TwitchUser.hpp @@ -34,43 +34,43 @@ struct TwitchUser { namespace pajlada { namespace Settings { -template <> -struct Deserialize { - static chatterino::TwitchUser get(const rapidjson::Value &value, - bool *error = nullptr) - { - using namespace chatterino; + template <> + struct Deserialize { + static chatterino::TwitchUser get(const rapidjson::Value &value, + bool *error = nullptr) + { + using namespace chatterino; - TwitchUser user; + TwitchUser user; + + if (!value.IsObject()) { + PAJLADA_REPORT_ERROR(error) + PAJLADA_THROW_EXCEPTION( + "Deserialized rapidjson::Value is wrong type"); + return user; + } + + if (!rj::getSafe(value, "_id", user.id)) { + PAJLADA_REPORT_ERROR(error) + PAJLADA_THROW_EXCEPTION("Missing ID key"); + return user; + } + + if (!rj::getSafe(value, "name", user.name)) { + PAJLADA_REPORT_ERROR(error) + PAJLADA_THROW_EXCEPTION("Missing name key"); + return user; + } + + if (!rj::getSafe(value, "display_name", user.displayName)) { + PAJLADA_REPORT_ERROR(error) + PAJLADA_THROW_EXCEPTION("Missing display name key"); + return user; + } - if (!value.IsObject()) { - PAJLADA_REPORT_ERROR(error) - PAJLADA_THROW_EXCEPTION( - "Deserialized rapidjson::Value is wrong type"); return user; } - - if (!rj::getSafe(value, "_id", user.id)) { - PAJLADA_REPORT_ERROR(error) - PAJLADA_THROW_EXCEPTION("Missing ID key"); - return user; - } - - if (!rj::getSafe(value, "name", user.name)) { - PAJLADA_REPORT_ERROR(error) - PAJLADA_THROW_EXCEPTION("Missing name key"); - return user; - } - - if (!rj::getSafe(value, "display_name", user.displayName)) { - PAJLADA_REPORT_ERROR(error) - PAJLADA_THROW_EXCEPTION("Missing display name key"); - return user; - } - - return user; - } -}; + }; } // namespace Settings } // namespace pajlada diff --git a/src/singletons/Emotes.cpp b/src/singletons/Emotes.cpp index cfc23f6d4..9916d2046 100644 --- a/src/singletons/Emotes.cpp +++ b/src/singletons/Emotes.cpp @@ -15,8 +15,6 @@ void Emotes::initialize(Settings &settings, Paths &paths) [] { getApp()->accounts->twitch.getCurrent()->loadEmotes(); }); this->emojis.load(); - this->bttv.loadGlobal(); - this->ffz.loadGlobal(); this->gifTimer.initialize(); } diff --git a/src/singletons/Emotes.hpp b/src/singletons/Emotes.hpp index e991fe674..fc475d752 100644 --- a/src/singletons/Emotes.hpp +++ b/src/singletons/Emotes.hpp @@ -25,8 +25,6 @@ public: bool isIgnoredEmote(const QString &emote); TwitchEmotes twitch; - BttvEmotes bttv; - FfzEmotes ffz; Emojis emojis; GIFTimer gifTimer; diff --git a/src/singletons/Fonts.cpp b/src/singletons/Fonts.cpp index 69f327285..991646acd 100644 --- a/src/singletons/Fonts.cpp +++ b/src/singletons/Fonts.cpp @@ -2,22 +2,23 @@ #include "Application.hpp" #include "debug/AssertInGuiThread.hpp" +#include "singletons/Settings.hpp" #include "singletons/WindowManager.hpp" #include #include #ifdef Q_OS_WIN32 -#define DEFAULT_FONT_FAMILY "Segoe UI" -#define DEFAULT_FONT_SIZE 10 +# define DEFAULT_FONT_FAMILY "Segoe UI" +# define DEFAULT_FONT_SIZE 10 #else -#ifdef Q_OS_MACOS -#define DEFAULT_FONT_FAMILY "Helvetica Neue" -#define DEFAULT_FONT_SIZE 12 -#else -#define DEFAULT_FONT_FAMILY "Arial" -#define DEFAULT_FONT_SIZE 11 -#endif +# ifdef Q_OS_MACOS +# define DEFAULT_FONT_FAMILY "Helvetica Neue" +# define DEFAULT_FONT_SIZE 12 +# else +# define DEFAULT_FONT_FAMILY "Arial" +# define DEFAULT_FONT_SIZE 11 +# endif #endif namespace chatterino { @@ -26,7 +27,7 @@ Fonts::Fonts() : chatFontFamily("/appearance/currentFontFamily", DEFAULT_FONT_FAMILY) , chatFontSize("/appearance/currentFontSize", DEFAULT_FONT_SIZE) { - this->fontsByType_.resize(size_t(EndType)); + this->fontsByType_.resize(size_t(FontStyle::EndType)); } void Fonts::initialize(Settings &, Paths &) @@ -61,21 +62,21 @@ void Fonts::initialize(Settings &, Paths &) }); } -QFont Fonts::getFont(Fonts::Type type, float scale) +QFont Fonts::getFont(FontStyle type, float scale) { return this->getOrCreateFontData(type, scale).font; } -QFontMetrics Fonts::getFontMetrics(Fonts::Type type, float scale) +QFontMetrics Fonts::getFontMetrics(FontStyle type, float scale) { return this->getOrCreateFontData(type, scale).metrics; } -Fonts::FontData &Fonts::getOrCreateFontData(Type type, float scale) +Fonts::FontData &Fonts::getOrCreateFontData(FontStyle type, float scale) { assertInGuiThread(); - assert(type >= 0 && type < EndType); + assert(type < FontStyle::EndType); auto &map = this->fontsByType_[size_t(type)]; @@ -94,23 +95,22 @@ Fonts::FontData &Fonts::getOrCreateFontData(Type type, float scale) return result.first->second; } -Fonts::FontData Fonts::createFontData(Type type, float scale) +Fonts::FontData Fonts::createFontData(FontStyle type, float scale) { // check if it's a chat (scale the setting) - if (type >= ChatStart && type <= ChatEnd) { - static std::unordered_map sizeScale{ - {ChatSmall, {0.6f, false, QFont::Normal}}, - {ChatMediumSmall, {0.8f, false, QFont::Normal}}, - {ChatMedium, {1, false, QFont::Normal}}, - {ChatMediumBold, - {1, false, - QFont::Weight(getApp()->settings->boldScale.getValue())}}, - {ChatMediumItalic, {1, true, QFont::Normal}}, - {ChatLarge, {1.2f, false, QFont::Normal}}, - {ChatVeryLarge, {1.4f, false, QFont::Normal}}, + if (type >= FontStyle::ChatStart && type <= FontStyle::ChatEnd) { + static std::unordered_map sizeScale{ + {FontStyle::ChatSmall, {0.6f, false, QFont::Normal}}, + {FontStyle::ChatMediumSmall, {0.8f, false, QFont::Normal}}, + {FontStyle::ChatMedium, {1, false, QFont::Normal}}, + {FontStyle::ChatMediumBold, + {1, false, QFont::Weight(getSettings()->boldScale.getValue())}}, + {FontStyle::ChatMediumItalic, {1, true, QFont::Normal}}, + {FontStyle::ChatLarge, {1.2f, false, QFont::Normal}}, + {FontStyle::ChatVeryLarge, {1.4f, false, QFont::Normal}}, }; - sizeScale[ChatMediumBold] = { - 1, false, QFont::Weight(getApp()->settings->boldScale.getValue())}; + sizeScale[FontStyle::ChatMediumBold] = { + 1, false, QFont::Weight(getSettings()->boldScale.getValue())}; auto data = sizeScale[type]; return FontData( QFont(QString::fromStdString(this->chatFontFamily.getValue()), @@ -126,11 +126,11 @@ Fonts::FontData Fonts::createFontData(Type type, float scale) constexpr float multiplier = 1.f; #endif - static std::unordered_map defaultSize{ - {Tiny, {8, "Monospace", false, QFont::Normal}}, - {UiMedium, + static std::unordered_map defaultSize{ + {FontStyle::Tiny, {8, "Monospace", false, QFont::Normal}}, + {FontStyle::UiMedium, {int(9 * multiplier), DEFAULT_FONT_FAMILY, false, QFont::Normal}}, - {UiTabs, + {FontStyle::UiTabs, {int(9 * multiplier), DEFAULT_FONT_FAMILY, false, QFont::Normal}}, }; diff --git a/src/singletons/Fonts.hpp b/src/singletons/Fonts.hpp index 59c6550c3..61ba393d6 100644 --- a/src/singletons/Fonts.hpp +++ b/src/singletons/Fonts.hpp @@ -16,6 +16,27 @@ namespace chatterino { class Settings; class Paths; +enum class FontStyle : uint8_t { + Tiny, + ChatSmall, + ChatMediumSmall, + ChatMedium, + ChatMediumBold, + ChatMediumItalic, + ChatLarge, + ChatVeryLarge, + + UiMedium, + UiTabs, + + // don't remove this value + EndType, + + // make sure to update these values accordingly! + ChatStart = ChatSmall, + ChatEnd = ChatVeryLarge, +}; + class Fonts final : public Singleton { public: @@ -24,29 +45,9 @@ public: virtual void initialize(Settings &settings, Paths &paths) override; // font data gets set in createFontData(...) - enum Type : uint8_t { - Tiny, - ChatSmall, - ChatMediumSmall, - ChatMedium, - ChatMediumBold, - ChatMediumItalic, - ChatLarge, - ChatVeryLarge, - UiMedium, - UiTabs, - - // don't remove this value - EndType, - - // make sure to update these values accordingly! - ChatStart = ChatSmall, - ChatEnd = ChatVeryLarge, - }; - - QFont getFont(Type type, float scale); - QFontMetrics getFontMetrics(Type type, float scale); + QFont getFont(FontStyle type, float scale); + QFontMetrics getFontMetrics(FontStyle type, float scale); pajlada::Settings::Setting chatFontFamily; pajlada::Settings::Setting chatFontSize; @@ -78,12 +79,10 @@ private: QFont::Weight weight; }; - FontData &getOrCreateFontData(Type type, float scale); - FontData createFontData(Type type, float scale); + FontData &getOrCreateFontData(FontStyle type, float scale); + FontData createFontData(FontStyle type, float scale); std::vector> fontsByType_; }; -using FontStyle = Fonts::Type; - } // namespace chatterino diff --git a/src/singletons/NativeMessaging.cpp b/src/singletons/NativeMessaging.cpp index cd80f8021..ed113f716 100644 --- a/src/singletons/NativeMessaging.cpp +++ b/src/singletons/NativeMessaging.cpp @@ -17,11 +17,11 @@ namespace ipc = boost::interprocess; #ifdef Q_OS_WIN -#include +# include -#include -#include "singletons/WindowManager.hpp" -#include "widgets/AttachedWindow.hpp" +# include +# include "singletons/WindowManager.hpp" +# include "widgets/AttachedWindow.hpp" #endif #include diff --git a/src/singletons/Paths.hpp b/src/singletons/Paths.hpp index 071feb737..70ab2642f 100644 --- a/src/singletons/Paths.hpp +++ b/src/singletons/Paths.hpp @@ -43,6 +43,6 @@ private: boost::optional portable_; }; -[[deprecated]] Paths *getPaths(); +Paths *getPaths(); } // namespace chatterino diff --git a/src/singletons/Settings.hpp b/src/singletons/Settings.hpp index 0dc8b6bda..aa0a9aa2f 100644 --- a/src/singletons/Settings.hpp +++ b/src/singletons/Settings.hpp @@ -5,7 +5,6 @@ #include "common/ChatterinoSetting.hpp" #include "controllers/highlights/HighlightPhrase.hpp" #include "controllers/moderationactions/ModerationAction.hpp" -#include "messages/MessageElement.hpp" #include #include @@ -26,6 +25,8 @@ public: /// Appearance BoolSetting showTimestamps = {"/appearance/messages/showTimestamps", true}; + BoolSetting enableAnimationsWhenFocused = { + "/appearance/enableAnimationsWhenFocused", false}; QStringSetting timestampFormat = {"/appearance/messages/timestampFormat", "h:mm"}; BoolSetting showBadges = {"/appearance/messages/showBadges", true}; diff --git a/src/singletons/Theme.cpp b/src/singletons/Theme.cpp index c5aa16b71..e39a6c873 100644 --- a/src/singletons/Theme.cpp +++ b/src/singletons/Theme.cpp @@ -10,21 +10,21 @@ namespace chatterino { namespace detail { -double getMultiplierByTheme(const QString &themeName) -{ - if (themeName == "Light") { - return 0.8; - } else if (themeName == "White") { - return 1.0; - } else if (themeName == "Black") { - return -1.0; - } else if (themeName == "Dark") { + double getMultiplierByTheme(const QString &themeName) + { + if (themeName == "Light") { + return 0.8; + } else if (themeName == "White") { + return 1.0; + } else if (themeName == "Black") { + return -1.0; + } else if (themeName == "Dark") { + return -0.8; + } + return -0.8; } - return -0.8; -} - } // namespace detail Theme::Theme() diff --git a/src/singletons/Updates.cpp b/src/singletons/Updates.cpp index d56a8f510..5782b4bea 100644 --- a/src/singletons/Updates.cpp +++ b/src/singletons/Updates.cpp @@ -1,6 +1,7 @@ #include "Updates.hpp" #include "common/NetworkRequest.hpp" +#include "common/Outcome.hpp" #include "common/Version.hpp" #include "singletons/Paths.hpp" #include "util/CombinePath.hpp" diff --git a/src/singletons/WindowManager.cpp b/src/singletons/WindowManager.cpp index 8ff34c7bf..02b711f7b 100644 --- a/src/singletons/WindowManager.cpp +++ b/src/singletons/WindowManager.cpp @@ -3,13 +3,20 @@ #include "Application.hpp" #include "debug/AssertInGuiThread.hpp" #include "debug/Log.hpp" +#include "messages/MessageElement.hpp" #include "providers/twitch/TwitchServer.hpp" #include "singletons/Fonts.hpp" #include "singletons/Paths.hpp" +#include "singletons/Settings.hpp" #include "singletons/Theme.hpp" #include "util/Clamp.hpp" #include "widgets/AccountSwitchPopupWidget.hpp" +#include "widgets/Notebook.hpp" +#include "widgets/Window.hpp" #include "widgets/dialogs/SettingsDialog.hpp" +#include "widgets/helper/NotebookTab.hpp" +#include "widgets/splits/Split.hpp" +#include "widgets/splits/SplitContainer.hpp" #include #include @@ -174,7 +181,7 @@ Window &WindowManager::getSelectedWindow() return *this->selectedWindow_; } -Window &WindowManager::createWindow(Window::Type type) +Window &WindowManager::createWindow(WindowType type) { assertInGuiThread(); @@ -182,7 +189,7 @@ Window &WindowManager::createWindow(Window::Type type) this->windows_.push_back(window); window->show(); - if (type != Window::Type::Main) { + if (type != WindowType::Main) { window->setAttribute(Qt::WA_DeleteOnClose); QObject::connect(window, &QWidget::destroyed, [this, window] { @@ -239,16 +246,16 @@ void WindowManager::initialize(Settings &settings, Paths &paths) // get type QString type_val = window_obj.value("type").toString(); - Window::Type type = - type_val == "main" ? Window::Type::Main : Window::Type::Popup; + WindowType type = + type_val == "main" ? WindowType::Main : WindowType::Popup; - if (type == Window::Type::Main && mainWindow_ != nullptr) { - type = Window::Type::Popup; + if (type == WindowType::Main && mainWindow_ != nullptr) { + type = WindowType::Popup; } Window &window = createWindow(type); - if (type == Window::Type::Main) { + if (type == WindowType::Main) { mainWindow_ = &window; } @@ -308,7 +315,7 @@ void WindowManager::initialize(Settings &settings, Paths &paths) } if (mainWindow_ == nullptr) { - mainWindow_ = &createWindow(Window::Type::Main); + mainWindow_ = &createWindow(WindowType::Main); mainWindow_->getNotebook().addPage(true); } @@ -344,15 +351,15 @@ void WindowManager::save() // window type switch (window->getType()) { - case Window::Type::Main: + case WindowType::Main: window_obj.insert("type", "main"); break; - case Window::Type::Popup: + case WindowType::Popup: window_obj.insert("type", "popup"); break; - case Window::Type::Attached:; + case WindowType::Attached:; } // window geometry @@ -399,7 +406,7 @@ void WindowManager::save() document.setObject(obj); // save file - QString settingsPath = app->paths->settingsDirectory + SETTINGS_FILENAME; + QString settingsPath = getPaths()->settingsDirectory + SETTINGS_FILENAME; QFile file(settingsPath); file.open(QIODevice::WriteOnly | QIODevice::Truncate); @@ -511,7 +518,7 @@ int WindowManager::clampUiScale(int scale) float WindowManager::getUiScaleValue() { - return getUiScaleValue(getApp()->settings->uiScale.getValue()); + return getUiScaleValue(getSettings()->uiScale.getValue()); } float WindowManager::getUiScaleValue(int scale) diff --git a/src/singletons/WindowManager.hpp b/src/singletons/WindowManager.hpp index eef2be2f1..74149876e 100644 --- a/src/singletons/WindowManager.hpp +++ b/src/singletons/WindowManager.hpp @@ -1,13 +1,20 @@ #pragma once +#include "common/Channel.hpp" +#include "common/FlagsEnum.hpp" #include "common/Singleton.hpp" -#include "widgets/Window.hpp" #include "widgets/splits/SplitContainer.hpp" namespace chatterino { class Settings; class Paths; +class Window; +class SplitContainer; + +enum class MessageElementFlag; +using MessageElementFlags = FlagsEnum; +enum class WindowType; class WindowManager final : public Singleton { @@ -34,7 +41,7 @@ public: Window &getMainWindow(); Window &getSelectedWindow(); - Window &createWindow(Window::Type type); + Window &createWindow(WindowType type); int windowCount(); Window *windowAt(int index); @@ -63,10 +70,10 @@ private: std::vector windows_; - Window *mainWindow_ = nullptr; - Window *selectedWindow_ = nullptr; + Window *mainWindow_{}; + Window *selectedWindow_{}; - MessageElementFlags wordFlags_ = MessageElementFlag::Default; + MessageElementFlags wordFlags_{}; pajlada::Settings::SettingListener wordFlagsListener_; }; diff --git a/src/singletons/helper/GifTimer.cpp b/src/singletons/helper/GifTimer.cpp index 49fbd98bf..8f3624523 100644 --- a/src/singletons/helper/GifTimer.cpp +++ b/src/singletons/helper/GifTimer.cpp @@ -10,19 +10,20 @@ void GIFTimer::initialize() { this->timer.setInterval(30); - getApp()->settings->enableGifAnimations.connect([this](bool enabled, auto) { - if (enabled) { + getSettings()->enableGifAnimations.connect([this](bool enabled, auto) { + if (enabled) this->timer.start(); - } else { + else this->timer.stop(); - } }); QObject::connect(&this->timer, &QTimer::timeout, [this] { + if (getSettings()->enableAnimationsWhenFocused && + qApp->activeWindow() == nullptr) + return; + this->signal.invoke(); - // fourtf: - auto app = getApp(); - app->windows->repaintGifEmotes(); + getApp()->windows->repaintGifEmotes(); }); } diff --git a/src/singletons/helper/LoggingChannel.cpp b/src/singletons/helper/LoggingChannel.cpp index d70344448..c9081d2a6 100644 --- a/src/singletons/helper/LoggingChannel.cpp +++ b/src/singletons/helper/LoggingChannel.cpp @@ -30,11 +30,11 @@ LoggingChannel::LoggingChannel(const QString &_channelName) auto app = getApp(); - app->settings->logPath.connect([this](const QString &logPath, auto) { + getSettings()->logPath.connect([this](const QString &logPath, auto) { auto app = getApp(); if (logPath.isEmpty()) { - this->baseDirectory = app->paths->messageLogDirectory; + this->baseDirectory = getPaths()->messageLogDirectory; } else { this->baseDirectory = logPath; } diff --git a/src/util/FormatTime.cpp b/src/util/FormatTime.cpp index 2eccca901..63b2e3c6b 100644 --- a/src/util/FormatTime.cpp +++ b/src/util/FormatTime.cpp @@ -2,11 +2,11 @@ namespace chatterino { namespace { -void appendDuration(int count, QChar &&order, QString &outString) -{ - outString.append(QString::number(count)); - outString.append(order); -} + void appendDuration(int count, QChar &&order, QString &outString) + { + outString.append(QString::number(count)); + outString.append(order); + } } // namespace QString formatTime(int totalSeconds) diff --git a/src/util/InitUpdateButton.cpp b/src/util/InitUpdateButton.cpp index 25d131635..54f06976d 100644 --- a/src/util/InitUpdateButton.cpp +++ b/src/util/InitUpdateButton.cpp @@ -6,15 +6,12 @@ namespace chatterino { void initUpdateButton(Button &button, - std::unique_ptr &handle, pajlada::Signals::SignalHolder &signalHolder) { button.hide(); // show update prompt when clicking the button - QObject::connect(&button, &Button::clicked, [&button, &handle] { - (void)(handle); - + QObject::connect(&button, &Button::clicked, [&button] { auto dialog = new UpdateDialog(); dialog->setActionOnFocusLoss(BaseWindow::Delete); dialog->move(button.mapToGlobal( diff --git a/src/util/InitUpdateButton.hpp b/src/util/InitUpdateButton.hpp index e95fcd7bd..c8caaf204 100644 --- a/src/util/InitUpdateButton.hpp +++ b/src/util/InitUpdateButton.hpp @@ -4,7 +4,7 @@ namespace pajlada { namespace Signals { -class SignalHolder; + class SignalHolder; } } // namespace pajlada @@ -14,7 +14,6 @@ class Button; class UpdateDialog; void initUpdateButton(Button &button, - std::unique_ptr &handle, pajlada::Signals::SignalHolder &signalHolder); } // namespace chatterino diff --git a/src/util/RapidJsonSerializeQString.hpp b/src/util/RapidJsonSerializeQString.hpp index 754cbf115..0d34a256b 100644 --- a/src/util/RapidJsonSerializeQString.hpp +++ b/src/util/RapidJsonSerializeQString.hpp @@ -6,38 +6,38 @@ namespace pajlada { namespace Settings { -template <> -struct Serialize { - static rapidjson::Value get(const QString &value, - rapidjson::Document::AllocatorType &a) - { - return rapidjson::Value(value.toUtf8(), a); - } -}; + template <> + struct Serialize { + static rapidjson::Value get(const QString &value, + rapidjson::Document::AllocatorType &a) + { + return rapidjson::Value(value.toUtf8(), a); + } + }; + + template <> + struct Deserialize { + static QString get(const rapidjson::Value &value, bool *error = nullptr) + { + if (!value.IsString()) { + PAJLADA_REPORT_ERROR(error) + PAJLADA_THROW_EXCEPTION( + "Deserialized rapidjson::Value is not a string"); + return QString{}; + } + + try { + return QString::fromUtf8(value.GetString(), + value.GetStringLength()); + } catch (const std::exception &) { + // int x = 5; + } catch (...) { + // int y = 5; + } -template <> -struct Deserialize { - static QString get(const rapidjson::Value &value, bool *error = nullptr) - { - if (!value.IsString()) { - PAJLADA_REPORT_ERROR(error) - PAJLADA_THROW_EXCEPTION( - "Deserialized rapidjson::Value is not a string"); return QString{}; } - - try { - return QString::fromUtf8(value.GetString(), - value.GetStringLength()); - } catch (const std::exception &) { - // int x = 5; - } catch (...) { - // int y = 5; - } - - return QString{}; - } -}; + }; } // namespace Settings } // namespace pajlada diff --git a/src/util/RapidjsonHelpers.cpp b/src/util/RapidjsonHelpers.cpp index 78256b4a2..2a9742df2 100644 --- a/src/util/RapidjsonHelpers.cpp +++ b/src/util/RapidjsonHelpers.cpp @@ -5,26 +5,28 @@ namespace chatterino { namespace rj { -void addMember(rapidjson::Value &obj, const char *key, rapidjson::Value &&value, - rapidjson::Document::AllocatorType &a) -{ - obj.AddMember(rapidjson::Value(key, a).Move(), value, a); -} + void addMember(rapidjson::Value &obj, const char *key, + rapidjson::Value &&value, + rapidjson::Document::AllocatorType &a) + { + obj.AddMember(rapidjson::Value(key, a).Move(), value, a); + } -void addMember(rapidjson::Value &obj, const char *key, rapidjson::Value &value, - rapidjson::Document::AllocatorType &a) -{ - obj.AddMember(rapidjson::Value(key, a).Move(), value.Move(), a); -} + void addMember(rapidjson::Value &obj, const char *key, + rapidjson::Value &value, + rapidjson::Document::AllocatorType &a) + { + obj.AddMember(rapidjson::Value(key, a).Move(), value.Move(), a); + } -std::string stringify(const rapidjson::Value &value) -{ - rapidjson::StringBuffer buffer; - rapidjson::Writer writer(buffer); - value.Accept(writer); + std::string stringify(const rapidjson::Value &value) + { + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + value.Accept(writer); - return std::string(buffer.GetString()); -} + return std::string(buffer.GetString()); + } } // namespace rj } // namespace chatterino diff --git a/src/util/RapidjsonHelpers.hpp b/src/util/RapidjsonHelpers.hpp index 8dbfe07a2..d3e73e8f9 100644 --- a/src/util/RapidjsonHelpers.hpp +++ b/src/util/RapidjsonHelpers.hpp @@ -11,91 +11,95 @@ namespace chatterino { namespace rj { -void addMember(rapidjson::Value &obj, const char *key, rapidjson::Value &&value, - rapidjson::Document::AllocatorType &a); -void addMember(rapidjson::Value &obj, const char *key, rapidjson::Value &value, - rapidjson::Document::AllocatorType &a); + void addMember(rapidjson::Value &obj, const char *key, + rapidjson::Value &&value, + rapidjson::Document::AllocatorType &a); + void addMember(rapidjson::Value &obj, const char *key, + rapidjson::Value &value, + rapidjson::Document::AllocatorType &a); -template -void set(rapidjson::Value &obj, const char *key, const Type &value, - rapidjson::Document::AllocatorType &a) -{ - assert(obj.IsObject()); + template + void set(rapidjson::Value &obj, const char *key, const Type &value, + rapidjson::Document::AllocatorType &a) + { + assert(obj.IsObject()); - addMember(obj, key, pajlada::Settings::Serialize::get(value, a), a); -} - -template <> -inline void set(rapidjson::Value &obj, const char *key, - const rapidjson::Value &value, - rapidjson::Document::AllocatorType &a) -{ - assert(obj.IsObject()); - - addMember(obj, key, const_cast(value), a); -} - -template -void set(rapidjson::Document &obj, const char *key, const Type &value) -{ - assert(obj.IsObject()); - - auto &a = obj.GetAllocator(); - - addMember(obj, key, pajlada::Settings::Serialize::get(value, a), a); -} - -template <> -inline void set(rapidjson::Document &obj, const char *key, - const rapidjson::Value &value) -{ - assert(obj.IsObject()); - - auto &a = obj.GetAllocator(); - - addMember(obj, key, const_cast(value), a); -} - -template -void add(rapidjson::Value &arr, const Type &value, - rapidjson::Document::AllocatorType &a) -{ - assert(arr.IsArray()); - - arr.PushBack(pajlada::Settings::Serialize::get(value, a), a); -} - -template -bool getSafe(const rapidjson::Value &obj, const char *key, Type &out) -{ - if (!obj.IsObject()) { - return false; + addMember(obj, key, pajlada::Settings::Serialize::get(value, a), + a); } - if (!obj.HasMember(key)) { - return false; + template <> + inline void set(rapidjson::Value &obj, const char *key, + const rapidjson::Value &value, + rapidjson::Document::AllocatorType &a) + { + assert(obj.IsObject()); + + addMember(obj, key, const_cast(value), a); } - if (obj.IsNull()) { - return false; + template + void set(rapidjson::Document &obj, const char *key, const Type &value) + { + assert(obj.IsObject()); + + auto &a = obj.GetAllocator(); + + addMember(obj, key, pajlada::Settings::Serialize::get(value, a), + a); } - bool error = false; - out = pajlada::Settings::Deserialize::get(obj[key], &error); + template <> + inline void set(rapidjson::Document &obj, const char *key, + const rapidjson::Value &value) + { + assert(obj.IsObject()); - return !error; -} + auto &a = obj.GetAllocator(); -template -bool getSafe(const rapidjson::Value &value, Type &out) -{ - bool error = false; - out = pajlada::Settings::Deserialize::get(value, &error); + addMember(obj, key, const_cast(value), a); + } - return !error; -} + template + void add(rapidjson::Value &arr, const Type &value, + rapidjson::Document::AllocatorType &a) + { + assert(arr.IsArray()); -std::string stringify(const rapidjson::Value &value); + arr.PushBack(pajlada::Settings::Serialize::get(value, a), a); + } + + template + bool getSafe(const rapidjson::Value &obj, const char *key, Type &out) + { + if (!obj.IsObject()) { + return false; + } + + if (!obj.HasMember(key)) { + return false; + } + + if (obj.IsNull()) { + return false; + } + + bool error = false; + out = pajlada::Settings::Deserialize::get(obj[key], &error); + + return !error; + } + + template + bool getSafe(const rapidjson::Value &value, Type &out) + { + bool error = false; + out = pajlada::Settings::Deserialize::get(value, &error); + + return !error; + } + + std::string stringify(const rapidjson::Value &value); } // namespace rj } // namespace chatterino diff --git a/src/util/StreamLink.cpp b/src/util/StreamLink.cpp index 2fd2ef0a3..972c47f59 100644 --- a/src/util/StreamLink.cpp +++ b/src/util/StreamLink.cpp @@ -2,6 +2,7 @@ #include "Application.hpp" #include "Helpers.hpp" +#include "debug/Log.hpp" #include "singletons/Settings.hpp" #include "widgets/dialogs/QualityPopup.hpp" @@ -15,88 +16,89 @@ namespace chatterino { namespace { -const char *getBinaryName() -{ + const char *getBinaryName() + { #ifdef _WIN32 - return "streamlink.exe"; + return "streamlink.exe"; #else - return "streamlink"; + return "streamlink"; #endif -} + } -const char *getDefaultBinaryPath() -{ + const char *getDefaultBinaryPath() + { #ifdef _WIN32 - return "C:\\Program Files (x86)\\Streamlink\\bin\\streamlink.exe"; + return "C:\\Program Files (x86)\\Streamlink\\bin\\streamlink.exe"; #else - return "/usr/bin/streamlink"; + return "/usr/bin/streamlink"; #endif -} - -QString getStreamlinkProgram() -{ - auto app = getApp(); - - if (app->settings->streamlinkUseCustomPath) { - return app->settings->streamlinkPath + "/" + getBinaryName(); - } else { - return getBinaryName(); - } -} - -bool checkStreamlinkPath(const QString &path) -{ - QFileInfo fileinfo(path); - - if (!fileinfo.exists()) { - return false; - // throw Exception(fS("Streamlink path ({}) is invalid, file does not - // exist", path)); } - return fileinfo.isExecutable(); -} + QString getStreamlinkProgram() + { + auto app = getApp(); -void showStreamlinkNotFoundError() -{ - static QErrorMessage *msg = new QErrorMessage; - - auto app = getApp(); - if (app->settings->streamlinkUseCustomPath) { - msg->showMessage( - "Unable to find Streamlink executable\nMake sure your custom path " - "is pointing " - "to the DIRECTORY where the streamlink executable is located"); - } else { - msg->showMessage( - "Unable to find Streamlink executable.\nIf you have Streamlink " - "installed, you might need to enable the custom path option"); - } -} - -QProcess *createStreamlinkProcess() -{ - auto p = new QProcess; - p->setProgram(getStreamlinkProgram()); - - QObject::connect(p, &QProcess::errorOccurred, [=](auto err) { - if (err == QProcess::FailedToStart) { - showStreamlinkNotFoundError(); + if (getSettings()->streamlinkUseCustomPath) { + return getSettings()->streamlinkPath + "/" + getBinaryName(); } else { - qDebug() << "Error occured: " << err; // + return getBinaryName(); + } + } + + bool checkStreamlinkPath(const QString &path) + { + QFileInfo fileinfo(path); + + if (!fileinfo.exists()) { + return false; + // throw Exception(fS("Streamlink path ({}) is invalid, file does + // not exist", path)); } - p->deleteLater(); - }); + return fileinfo.isExecutable(); + } - QObject::connect(p, - static_cast(&QProcess::finished), - [=](int res) { - p->deleteLater(); // - }); + void showStreamlinkNotFoundError() + { + static QErrorMessage *msg = new QErrorMessage; - return p; -} + auto app = getApp(); + if (getSettings()->streamlinkUseCustomPath) { + msg->showMessage( + "Unable to find Streamlink executable\nMake sure your custom " + "path " + "is pointing " + "to the DIRECTORY where the streamlink executable is located"); + } else { + msg->showMessage( + "Unable to find Streamlink executable.\nIf you have Streamlink " + "installed, you might need to enable the custom path option"); + } + } + + QProcess *createStreamlinkProcess() + { + auto p = new QProcess; + p->setProgram(getStreamlinkProgram()); + + QObject::connect(p, &QProcess::errorOccurred, [=](auto err) { + if (err == QProcess::FailedToStart) { + showStreamlinkNotFoundError(); + } else { + log("Error occured {}", err); + } + + p->deleteLater(); + }); + + QObject::connect( + p, static_cast(&QProcess::finished), + [=](int res) { + p->deleteLater(); // + }); + + return p; + } } // namespace @@ -109,7 +111,7 @@ void getStreamQualities(const QString &channelURL, p, static_cast(&QProcess::finished), [=](int res) { if (res != 0) { - qDebug() << "Got error code" << res; + log("Got error code {}", res); // return; } QString lastLine = QString(p->readAllStandardOutput()); @@ -121,7 +123,15 @@ void getStreamQualities(const QString &channelURL, for (int i = split.length() - 1; i >= 0; i--) { QString option = split.at(i); - if (option.endsWith(" (worst)")) { + if (option == "best)") { + // As it turns out, sometimes, one quality option can + // be the best and worst quality at the same time. + // Since we start loop from the end, we can check + // that and act accordingly + option = split.at(--i); + // "900p60 (worst" + options << option.left(option.length() - 7); + } else if (option.endsWith(" (worst)")) { options << option.left(option.length() - 8); } else if (option.endsWith(" (best)")) { options << option.left(option.length() - 7); @@ -146,9 +156,9 @@ void openStreamlink(const QString &channelURL, const QString &quality, QStringList arguments; - QString additionalOptions = app->settings->streamlinkOpts.getValue(); + QString additionalOptions = getSettings()->streamlinkOpts.getValue(); if (!additionalOptions.isEmpty()) { - arguments << app->settings->streamlinkOpts; + arguments << getSettings()->streamlinkOpts; } arguments.append(extraArguments); @@ -173,7 +183,7 @@ void openStreamlinkForChannel(const QString &channel) QString channelURL = "twitch.tv/" + channel; - QString preferredQuality = app->settings->preferredQuality; + QString preferredQuality = getSettings()->preferredQuality; preferredQuality = preferredQuality.toLower(); if (preferredQuality == "choose") { diff --git a/src/util/WindowsHelper.hpp b/src/util/WindowsHelper.hpp index fdbafa9c2..f02bb1362 100644 --- a/src/util/WindowsHelper.hpp +++ b/src/util/WindowsHelper.hpp @@ -2,8 +2,8 @@ #ifdef USEWINSDK -#include -#include +# include +# include namespace chatterino { diff --git a/src/widgets/AccountSwitchWidget.cpp b/src/widgets/AccountSwitchWidget.cpp index 9c307ba73..3e201ded3 100644 --- a/src/widgets/AccountSwitchWidget.cpp +++ b/src/widgets/AccountSwitchWidget.cpp @@ -3,6 +3,7 @@ #include "Application.hpp" #include "common/Common.hpp" #include "controllers/accounts/AccountController.hpp" +#include "providers/twitch/TwitchAccount.hpp" #include "providers/twitch/TwitchCommon.hpp" namespace chatterino { diff --git a/src/widgets/AttachedWindow.cpp b/src/widgets/AttachedWindow.cpp index b247129b6..fee940591 100644 --- a/src/widgets/AttachedWindow.cpp +++ b/src/widgets/AttachedWindow.cpp @@ -8,12 +8,12 @@ #include #ifdef USEWINSDK -#include "util/WindowsHelper.hpp" +# include "util/WindowsHelper.hpp" -#include "Windows.h" +# include "Windows.h" // don't even think about reordering these -#include "Psapi.h" -#pragma comment(lib, "Dwmapi.lib") +# include "Psapi.h" +# pragma comment(lib, "Dwmapi.lib") #endif namespace chatterino { diff --git a/src/widgets/AttachedWindow.hpp b/src/widgets/AttachedWindow.hpp index 80b030fca..64da52f6a 100644 --- a/src/widgets/AttachedWindow.hpp +++ b/src/widgets/AttachedWindow.hpp @@ -1,12 +1,14 @@ #pragma once +#include #include -#include "common/Channel.hpp" -#include "widgets/splits/Split.hpp" - namespace chatterino { +class Split; +class Channel; +using ChannelPtr = std::shared_ptr; + class AttachedWindow : public QWidget { AttachedWindow(void *_target, int _yOffset); diff --git a/src/widgets/BaseWindow.cpp b/src/widgets/BaseWindow.cpp index eccde0f97..6394284e0 100644 --- a/src/widgets/BaseWindow.cpp +++ b/src/widgets/BaseWindow.cpp @@ -4,6 +4,7 @@ #include "boost/algorithm/algorithm.hpp" #include "debug/Log.hpp" #include "singletons/Settings.hpp" +#include "singletons/Theme.hpp" #include "singletons/WindowManager.hpp" #include "util/PostToThread.hpp" #include "util/WindowsHelper.hpp" @@ -19,20 +20,20 @@ #include #ifdef USEWINSDK -#include -#include -#include -#include -#include -#include +# include +# include +# include +# include +# include +# include //#include -#pragma comment(lib, "Dwmapi.lib") +# pragma comment(lib, "Dwmapi.lib") -#include -#include +# include +# include -#define WM_DPICHANGED 0x02E0 +# define WM_DPICHANGED 0x02E0 #endif #include "widgets/helper/TitlebarButton.hpp" @@ -55,13 +56,13 @@ BaseWindow::BaseWindow(QWidget *parent, Flags _flags) this->init(); this->connections_.managedConnect( - getApp()->settings->uiScale.getValueChangedSignal(), + getSettings()->uiScale.getValueChangedSignal(), [this](auto, auto) { postToThread([this] { this->updateScale(); }); }); this->updateScale(); createWindowShortcut(this, "CTRL+0", - [] { getApp()->settings->uiScale.setValue(0); }); + [] { getSettings()->uiScale.setValue(0); }); // QTimer::this->scaleChangedEvent(this->getScale()); } @@ -110,11 +111,11 @@ void BaseWindow::init() // buttons TitleBarButton *_minButton = new TitleBarButton; - _minButton->setButtonStyle(TitleBarButton::Minimize); + _minButton->setButtonStyle(TitleBarButtonStyle::Minimize); TitleBarButton *_maxButton = new TitleBarButton; - _maxButton->setButtonStyle(TitleBarButton::Maximize); + _maxButton->setButtonStyle(TitleBarButtonStyle::Maximize); TitleBarButton *_exitButton = new TitleBarButton; - _exitButton->setButtonStyle(TitleBarButton::Close); + _exitButton->setButtonStyle(TitleBarButtonStyle::Close); QObject::connect(_minButton, &TitleBarButton::clicked, this, [this] { @@ -162,7 +163,7 @@ void BaseWindow::init() // fourtf: don't ask me why we need to delay this if (!(this->flags_ & Flags::TopMost)) { QTimer::singleShot(1, this, [this] { - getApp()->settings->windowTopMost.connect( + getSettings()->windowTopMost.connect( [this](bool topMost, auto) { ::SetWindowPos(HWND(this->winId()), topMost ? HWND_TOPMOST : HWND_NOTOPMOST, 0, @@ -173,7 +174,7 @@ void BaseWindow::init() }); } #else -// if (getApp()->settings->windowTopMost.getValue()) { +// if (getSettings()->windowTopMost.getValue()) { // this->setWindowFlag(Qt::WindowStaysOnTopHint); // } #endif @@ -266,11 +267,11 @@ void BaseWindow::wheelEvent(QWheelEvent *event) if (event->modifiers() & Qt::ControlModifier) { if (event->delta() > 0) { - getApp()->settings->uiScale.setValue(WindowManager::clampUiScale( - getApp()->settings->uiScale.getValue() + 1)); + getSettings()->uiScale.setValue(WindowManager::clampUiScale( + getSettings()->uiScale.getValue() + 1)); } else { - getApp()->settings->uiScale.setValue(WindowManager::clampUiScale( - getApp()->settings->uiScale.getValue() - 1)); + getSettings()->uiScale.setValue(WindowManager::clampUiScale( + getSettings()->uiScale.getValue() - 1)); } } } @@ -315,7 +316,7 @@ void BaseWindow::mousePressEvent(QMouseEvent *event) }; if (!recursiveCheckMouseTracking(widget)) { - Log("Start moving"); + log("Start moving"); this->moving = true; } } @@ -330,7 +331,7 @@ void BaseWindow::mouseReleaseEvent(QMouseEvent *event) #ifndef Q_OS_WIN if (this->flags_ & FramelessDraggable) { if (this->moving) { - Log("Stop moving"); + log("Stop moving"); this->moving = false; } } @@ -353,8 +354,8 @@ void BaseWindow::mouseMoveEvent(QMouseEvent *event) BaseWidget::mouseMoveEvent(event); } -TitleBarButton *BaseWindow::addTitleBarButton( - const TitleBarButton::Style &style, std::function onClicked) +TitleBarButton *BaseWindow::addTitleBarButton(const TitleBarButtonStyle &style, + std::function onClicked) { TitleBarButton *button = new TitleBarButton; button->setScaleIndependantSize(30, 30); @@ -389,10 +390,10 @@ void BaseWindow::changeEvent(QEvent *) #ifdef USEWINSDK if (this->ui_.maxButton) { - this->ui_.maxButton->setButtonStyle(this->windowState() & - Qt::WindowMaximized - ? TitleBarButton::Unmaximize - : TitleBarButton::Maximize); + this->ui_.maxButton->setButtonStyle( + this->windowState() & Qt::WindowMaximized + ? TitleBarButtonStyle::Unmaximize + : TitleBarButtonStyle::Maximize); } #endif @@ -459,11 +460,11 @@ bool BaseWindow::nativeEvent(const QByteArray &eventType, void *message, long *result) { #ifdef USEWINSDK -#if (QT_VERSION == QT_VERSION_CHECK(5, 11, 1)) +# if (QT_VERSION == QT_VERSION_CHECK(5, 11, 1)) MSG *msg = *reinterpret_cast(message); -#else +# else MSG *msg = reinterpret_cast(message); -#endif +# endif bool returnValue = false; diff --git a/src/widgets/BaseWindow.hpp b/src/widgets/BaseWindow.hpp index eac2ee55e..088c07c86 100644 --- a/src/widgets/BaseWindow.hpp +++ b/src/widgets/BaseWindow.hpp @@ -1,7 +1,6 @@ #pragma once #include "BaseWidget.hpp" -#include "widgets/helper/TitlebarButton.hpp" #include #include @@ -15,6 +14,7 @@ namespace chatterino { class Button; class EffectLabel; class TitleBarButton; +enum class TitleBarButtonStyle; class BaseWindow : public BaseWidget { @@ -36,7 +36,7 @@ public: QWidget *getLayoutContainer(); bool hasCustomWindowFrame(); - TitleBarButton *addTitleBarButton(const TitleBarButton::Style &style, + TitleBarButton *addTitleBarButton(const TitleBarButtonStyle &style, std::function onClicked); EffectLabel *addTitleBarLabel(std::function onClicked); @@ -114,6 +114,6 @@ private: pajlada::Signals::SignalHolder connections_; std::vector managedConnections_; -}; +}; // namespace chatterino } // namespace chatterino diff --git a/src/widgets/Notebook.cpp b/src/widgets/Notebook.cpp index d99d1f021..c56fba8b5 100644 --- a/src/widgets/Notebook.cpp +++ b/src/widgets/Notebook.cpp @@ -2,6 +2,7 @@ #include "Application.hpp" #include "debug/Log.hpp" +#include "singletons/Settings.hpp" #include "singletons/Theme.hpp" #include "singletons/WindowManager.hpp" #include "util/InitUpdateButton.hpp" @@ -27,11 +28,11 @@ namespace chatterino { Notebook::Notebook(QWidget *parent) : BaseWidget(parent) - , addButton_(this) + , addButton_(new NotebookButton(this)) { - this->addButton_.setIcon(NotebookButton::Icon::Plus); + this->addButton_->setIcon(NotebookButton::Icon::Plus); - this->addButton_.setHidden(true); + this->addButton_->setHidden(true); auto *shortcut_next = new QShortcut(QKeySequence("Ctrl+Tab"), this); QObject::connect(shortcut_next, &QShortcut::activated, @@ -290,14 +291,14 @@ void Notebook::setShowAddButton(bool value) { this->showAddButton_ = value; - this->addButton_.setHidden(!value); + this->addButton_->setHidden(!value); } void Notebook::scaleChangedEvent(float scale) { float h = NOTEBOOK_TAB_HEIGHT * this->getScale(); - this->addButton_.setFixedSize(h, h); + this->addButton_->setFixedSize(h, h); for (auto &i : this->items_) { i.tab->updateSize(); @@ -353,7 +354,7 @@ void Notebook::performLayout(bool animated) } if (this->showAddButton_) { - this->addButton_.move(x, y); + this->addButton_->move(x, y); } if (this->lineY_ != y + tabHeight) { @@ -368,7 +369,7 @@ void Notebook::performLayout(bool animated) } if (this->showAddButton_) { - this->addButton_.raise(); + this->addButton_->raise(); } if (this->selectedPage_ != nullptr) { @@ -389,7 +390,7 @@ void Notebook::paintEvent(QPaintEvent *event) NotebookButton *Notebook::getAddButton() { - return &this->addButton_; + return this->addButton_; } NotebookButton *Notebook::addCustomButton() @@ -432,10 +433,9 @@ void SplitNotebook::addCustomButtons() // settings auto settingsBtn = this->addCustomButton(); - settingsBtn->setVisible( - !getApp()->settings->hidePreferencesButton.getValue()); + settingsBtn->setVisible(!getSettings()->hidePreferencesButton.getValue()); - getApp()->settings->hidePreferencesButton.connect( + getSettings()->hidePreferencesButton.connect( [settingsBtn](bool hide, auto) { settingsBtn->setVisible(!hide); }, this->connections_); @@ -446,8 +446,8 @@ void SplitNotebook::addCustomButtons() // account auto userBtn = this->addCustomButton(); - userBtn->setVisible(!getApp()->settings->hideUserButton.getValue()); - getApp()->settings->hideUserButton.connect( + userBtn->setVisible(!getSettings()->hideUserButton.getValue()); + getSettings()->hideUserButton.connect( [userBtn](bool hide, auto) { userBtn->setVisible(!hide); }, this->connections_); @@ -460,8 +460,7 @@ void SplitNotebook::addCustomButtons() // updates auto updateBtn = this->addCustomButton(); - initUpdateButton(*updateBtn, this->updateDialogHandle_, - this->signalHolder_); + initUpdateButton(*updateBtn, this->signalHolder_); } SplitContainer *SplitNotebook::addPage(bool select) diff --git a/src/widgets/Notebook.hpp b/src/widgets/Notebook.hpp index 749d12594..022c075a2 100644 --- a/src/widgets/Notebook.hpp +++ b/src/widgets/Notebook.hpp @@ -2,10 +2,6 @@ #include "pajlada/signals/signal.hpp" #include "widgets/BaseWidget.hpp" -#include "widgets/dialogs/UpdateDialog.hpp" -#include "widgets/helper/NotebookButton.hpp" -#include "widgets/helper/NotebookTab.hpp" -#include "widgets/splits/SplitContainer.hpp" #include #include @@ -15,6 +11,10 @@ namespace chatterino { class Window; +class UpdateDialog; +class NotebookButton; +class NotebookTab; +class SplitContainer; class Notebook : public BaseWidget { @@ -22,6 +22,7 @@ class Notebook : public BaseWidget public: explicit Notebook(QWidget *parent); + ~Notebook() override = default; NotebookTab *addPage(QWidget *page, QString title = QString(), bool select = false); @@ -74,7 +75,7 @@ private: QList items_; QWidget *selectedPage_ = nullptr; - NotebookButton addButton_; + NotebookButton *addButton_; std::vector customButtons_; bool allowUserTabManagement_ = false; @@ -94,7 +95,7 @@ private: void addCustomButtons(); pajlada::Signals::SignalHolder signalHolder_; - std::unique_ptr updateDialogHandle_; + std::shared_ptr updateDialogHandle_; std::vector connections_; }; diff --git a/src/widgets/Scrollbar.cpp b/src/widgets/Scrollbar.cpp index 9c46cff80..879aa9af2 100644 --- a/src/widgets/Scrollbar.cpp +++ b/src/widgets/Scrollbar.cpp @@ -1,6 +1,7 @@ #include "widgets/Scrollbar.hpp" #include "Application.hpp" +#include "singletons/Settings.hpp" #include "singletons/Theme.hpp" #include "widgets/helper/ChannelView.hpp" @@ -103,8 +104,7 @@ void Scrollbar::setSmallChange(qreal value) void Scrollbar::setDesiredValue(qreal value, bool animated) { - auto app = getApp(); - animated &= app->settings->enableSmoothScrolling.getValue(); + animated &= getSettings()->enableSmoothScrolling.getValue(); value = std::max(this->minimum_, std::min(this->maximum_ - this->largeChange_, value)); @@ -221,8 +221,6 @@ void Scrollbar::printCurrentState(const QString &prefix) const void Scrollbar::paintEvent(QPaintEvent *) { - auto *app = getApp(); - bool mouseOver = this->mouseOverIndex_ != -1; int xOffset = mouseOver ? 0 : width() - int(4 * this->getScale()); @@ -267,9 +265,11 @@ void Scrollbar::paintEvent(QPaintEvent *) QColor color = [&] { switch (highlight.getColor()) { case ScrollbarHighlight::Highlight: - return app->themes->scrollbars.highlights.highlight; + return getApp() + ->themes->scrollbars.highlights.highlight; case ScrollbarHighlight::Subscription: - return app->themes->scrollbars.highlights.subscription; + return getApp() + ->themes->scrollbars.highlights.subscription; } return QColor(); }(); diff --git a/src/widgets/Scrollbar.hpp b/src/widgets/Scrollbar.hpp index fe72477c3..114a1dfe6 100644 --- a/src/widgets/Scrollbar.hpp +++ b/src/widgets/Scrollbar.hpp @@ -1,7 +1,6 @@ #pragma once #include "messages/LimitedQueue.hpp" -#include "singletons/Settings.hpp" #include "widgets/BaseWidget.hpp" #include "widgets/helper/ScrollbarHighlight.hpp" diff --git a/src/widgets/StreamView.cpp b/src/widgets/StreamView.cpp index ad3f1966c..f548deceb 100644 --- a/src/widgets/StreamView.cpp +++ b/src/widgets/StreamView.cpp @@ -3,10 +3,11 @@ #include "common/Channel.hpp" #include "util/Helpers.hpp" #include "util/LayoutCreator.hpp" +#include "widgets/helper/ChannelView.hpp" #include "widgets/splits/Split.hpp" #ifdef USEWEBENGINE -#include +# include #endif namespace chatterino { diff --git a/src/widgets/TooltipWidget.cpp b/src/widgets/TooltipWidget.cpp index 0b3b83639..084f729ac 100644 --- a/src/widgets/TooltipWidget.cpp +++ b/src/widgets/TooltipWidget.cpp @@ -10,7 +10,7 @@ #include #ifdef USEWINSDK -#include +# include #endif namespace chatterino { @@ -79,7 +79,7 @@ void TooltipWidget::updateFont() auto app = getApp(); this->setFont( - app->fonts->getFont(Fonts::Type::ChatMediumSmall, this->getScale())); + app->fonts->getFont(FontStyle::ChatMediumSmall, this->getScale())); } void TooltipWidget::setText(QString text) diff --git a/src/widgets/Window.cpp b/src/widgets/Window.cpp index 7088e5cb6..6df88b402 100644 --- a/src/widgets/Window.cpp +++ b/src/widgets/Window.cpp @@ -14,8 +14,11 @@ #include "widgets/dialogs/SettingsDialog.hpp" #include "widgets/dialogs/UpdateDialog.hpp" #include "widgets/dialogs/WelcomeDialog.hpp" +#include "widgets/helper/EffectLabel.hpp" #include "widgets/helper/Shortcut.hpp" +#include "widgets/helper/TitlebarButton.hpp" #include "widgets/splits/Split.hpp" +#include "widgets/splits/SplitContainer.hpp" #include #include @@ -28,10 +31,10 @@ namespace chatterino { -Window::Window(Type type) +Window::Window(WindowType type) : BaseWindow(nullptr, BaseWindow::EnableCustomFrame) , type_(type) - , notebook_(this) + , notebook_(new SplitNotebook(this)) { this->addCustomTitlebarButtons(); this->addDebugStuff(); @@ -42,26 +45,26 @@ Window::Window(Type type) [this] { this->onAccountSelected(); }); this->onAccountSelected(); - if (type == Type::Main) { + if (type == WindowType::Main) { this->resize(int(600 * this->getScale()), int(500 * this->getScale())); } else { this->resize(int(300 * this->getScale()), int(500 * this->getScale())); } } -Window::Type Window::getType() +WindowType Window::getType() { return this->type_; } SplitNotebook &Window::getNotebook() { - return this->notebook_; + return *this->notebook_; } void Window::repaintVisibleChatWidgets(Channel *channel) { - auto page = this->notebook_.getOrAddSelectedPage(); + auto page = this->notebook_->getOrAddSelectedPage(); for (const auto &split : page->getSplits()) { if (channel == nullptr || channel == split->getChannel().get()) { @@ -77,7 +80,7 @@ bool Window::event(QEvent *event) break; case QEvent::WindowDeactivate: { - auto page = this->notebook_.getOrAddSelectedPage(); + auto page = this->notebook_->getOrAddSelectedPage(); if (page != nullptr) { std::vector splits = page->getSplits(); @@ -102,8 +105,8 @@ bool Window::event(QEvent *event) void Window::showEvent(QShowEvent *event) { // Startup notification - if (getApp()->settings->startUpNotification.getValue() < 1) { - getApp()->settings->startUpNotification = 1; + if (getSettings()->startUpNotification.getValue() < 1) { + getSettings()->startUpNotification = 1; auto box = new QMessageBox( QMessageBox::Information, "Chatterino 2 Beta", @@ -115,8 +118,8 @@ void Window::showEvent(QShowEvent *event) } // Show changelog - if (getApp()->settings->currentVersion.getValue() != "" && - getApp()->settings->currentVersion.getValue() != CHATTERINO_VERSION) { + if (getSettings()->currentVersion.getValue() != "" && + getSettings()->currentVersion.getValue() != CHATTERINO_VERSION) { auto box = new QMessageBox(QMessageBox::Information, "Chatterino 2 Beta", "Show changelog?", QMessageBox::Yes | QMessageBox::No); @@ -127,7 +130,7 @@ void Window::showEvent(QShowEvent *event) } } - getApp()->settings->currentVersion.setValue(CHATTERINO_VERSION); + getSettings()->currentVersion.setValue(CHATTERINO_VERSION); // -- BaseWindow::showEvent(event); @@ -135,7 +138,7 @@ void Window::showEvent(QShowEvent *event) void Window::closeEvent(QCloseEvent *) { - if (this->type_ == Type::Main) { + if (this->type_ == WindowType::Main) { auto app = getApp(); app->windows->save(); app->windows->closeAll(); @@ -143,7 +146,7 @@ void Window::closeEvent(QCloseEvent *) this->closed.invoke(); - if (this->type_ == Type::Main) { + if (this->type_ == WindowType::Main) { QApplication::exit(); } } @@ -152,35 +155,34 @@ void Window::addLayout() { QVBoxLayout *layout = new QVBoxLayout(this); - layout->addWidget(&this->notebook_); + layout->addWidget(this->notebook_); this->getLayoutContainer()->setLayout(layout); // set margin layout->setMargin(0); - this->notebook_.setAllowUserTabManagement(true); - this->notebook_.setShowAddButton(true); + this->notebook_->setAllowUserTabManagement(true); + this->notebook_->setShowAddButton(true); } void Window::addCustomTitlebarButtons() { if (!this->hasCustomWindowFrame()) return; - if (this->type_ != Type::Main) return; + if (this->type_ != WindowType::Main) return; // settings - this->addTitleBarButton(TitleBarButton::Settings, [] { - getApp()->windows->showSettingsDialog(); // - }); + this->addTitleBarButton(TitleBarButtonStyle::Settings, + [] { getApp()->windows->showSettingsDialog(); }); // updates - auto update = this->addTitleBarButton(TitleBarButton::None, [] {}); + auto update = this->addTitleBarButton(TitleBarButtonStyle::None, [] {}); - initUpdateButton(*update, this->updateDialogHandle_, this->signalHolder_); + initUpdateButton(*update, this->signalHolder_); // account this->userLabel_ = this->addTitleBarLabel([this] { getApp()->windows->showAccountSelectPopup(this->userLabel_->mapToGlobal( - this->userLabel_->rect().bottomLeft())); // + this->userLabel_->rect().bottomLeft())); }); this->userLabel_->setMinimumWidth(20 * getScale()); } @@ -252,35 +254,35 @@ void Window::addShortcuts() // Switch tab createWindowShortcut(this, "CTRL+T", [this] { - this->notebook_.getOrAddSelectedPage()->appendNewSplit(true); + this->notebook_->getOrAddSelectedPage()->appendNewSplit(true); }); createWindowShortcut(this, "CTRL+1", - [this] { this->notebook_.selectIndex(0); }); + [this] { this->notebook_->selectIndex(0); }); createWindowShortcut(this, "CTRL+2", - [this] { this->notebook_.selectIndex(1); }); + [this] { this->notebook_->selectIndex(1); }); createWindowShortcut(this, "CTRL+3", - [this] { this->notebook_.selectIndex(2); }); + [this] { this->notebook_->selectIndex(2); }); createWindowShortcut(this, "CTRL+4", - [this] { this->notebook_.selectIndex(3); }); + [this] { this->notebook_->selectIndex(3); }); createWindowShortcut(this, "CTRL+5", - [this] { this->notebook_.selectIndex(4); }); + [this] { this->notebook_->selectIndex(4); }); createWindowShortcut(this, "CTRL+6", - [this] { this->notebook_.selectIndex(5); }); + [this] { this->notebook_->selectIndex(5); }); createWindowShortcut(this, "CTRL+7", - [this] { this->notebook_.selectIndex(6); }); + [this] { this->notebook_->selectIndex(6); }); createWindowShortcut(this, "CTRL+8", - [this] { this->notebook_.selectIndex(7); }); + [this] { this->notebook_->selectIndex(7); }); createWindowShortcut(this, "CTRL+9", - [this] { this->notebook_.selectIndex(8); }); + [this] { this->notebook_->selectIndex(8); }); // Zoom in { auto s = new QShortcut(QKeySequence::ZoomIn, this); s->setContext(Qt::WindowShortcut); QObject::connect(s, &QShortcut::activated, this, [] { - getApp()->settings->uiScale.setValue(WindowManager::clampUiScale( - getApp()->settings->uiScale.getValue() + 1)); + getSettings()->uiScale.setValue(WindowManager::clampUiScale( + getSettings()->uiScale.getValue() + 1)); }); } @@ -289,18 +291,18 @@ void Window::addShortcuts() auto s = new QShortcut(QKeySequence::ZoomOut, this); s->setContext(Qt::WindowShortcut); QObject::connect(s, &QShortcut::activated, this, [] { - getApp()->settings->uiScale.setValue(WindowManager::clampUiScale( - getApp()->settings->uiScale.getValue() - 1)); + getSettings()->uiScale.setValue(WindowManager::clampUiScale( + getSettings()->uiScale.getValue() - 1)); }); } // New tab createWindowShortcut(this, "CTRL+SHIFT+T", - [this] { this->notebook_.addPage(true); }); + [this] { this->notebook_->addPage(true); }); // Close tab createWindowShortcut(this, "CTRL+SHIFT+W", - [this] { this->notebook_.removeCurrentPage(); }); + [this] { this->notebook_->removeCurrentPage(); }); } void Window::onAccountSelected() diff --git a/src/widgets/Window.hpp b/src/widgets/Window.hpp index 438f6b3e2..f9267a81f 100644 --- a/src/widgets/Window.hpp +++ b/src/widgets/Window.hpp @@ -1,13 +1,6 @@ #pragma once -#include "util/Helpers.hpp" #include "widgets/BaseWindow.hpp" -#include "widgets/Notebook.hpp" -#include "widgets/dialogs/UpdateDialog.hpp" - -//#ifdef USEWINSDK -//#include -//#endif #include #include @@ -16,17 +9,20 @@ namespace chatterino { class Theme; +class UpdateDialog; +class SplitNotebook; +class Channel; + +enum class WindowType { Main, Popup, Attached }; class Window : public BaseWindow { Q_OBJECT public: - enum class Type { Main, Popup, Attached }; + explicit Window(WindowType type); - explicit Window(Window::Type type); - - Type getType(); + WindowType getType(); SplitNotebook &getNotebook(); void repaintVisibleChatWidgets(Channel *channel = nullptr); @@ -45,11 +41,11 @@ private: void addLayout(); void onAccountSelected(); - Type type_; + WindowType type_; - SplitNotebook notebook_; + SplitNotebook *notebook_; EffectLabel *userLabel_ = nullptr; - std::unique_ptr updateDialogHandle_; + std::shared_ptr updateDialogHandle_; pajlada::Signals::SignalHolder signalHolder_; diff --git a/src/widgets/dialogs/EmotePopup.cpp b/src/widgets/dialogs/EmotePopup.cpp index 8c3fc87e1..2fe6bb28a 100644 --- a/src/widgets/dialogs/EmotePopup.cpp +++ b/src/widgets/dialogs/EmotePopup.cpp @@ -3,9 +3,12 @@ #include "Application.hpp" #include "controllers/accounts/AccountController.hpp" #include "debug/Benchmark.hpp" +#include "messages/Message.hpp" #include "messages/MessageBuilder.hpp" #include "providers/twitch/TwitchChannel.hpp" +#include "singletons/Emotes.hpp" #include "widgets/Notebook.hpp" +#include "widgets/helper/ChannelView.hpp" #include #include @@ -13,55 +16,57 @@ namespace chatterino { namespace { -auto makeTitleMessage(const QString &title) -{ - MessageBuilder builder; - builder.emplace(title, MessageElementFlag::Text); - builder->flags.set(MessageFlag::Centered); - return builder.release(); -} -auto makeEmoteMessage(const EmoteMap &map) -{ - MessageBuilder builder; - builder->flags.set(MessageFlag::Centered); - builder->flags.set(MessageFlag::DisableCompactEmotes); - - for (const auto &emote : map) { - builder - .emplace(emote.second, MessageElementFlag::AlwaysShow) - ->setLink(Link(Link::InsertText, emote.first.string)); + auto makeTitleMessage(const QString &title) + { + MessageBuilder builder; + builder.emplace(title, MessageElementFlag::Text); + builder->flags.set(MessageFlag::Centered); + return builder.release(); } - - return builder.release(); -} -void addEmoteSets(std::vector> sets, - Channel &globalChannel, Channel &subChannel) -{ - for (const auto &set : sets) { - auto &channel = set->key == "0" ? globalChannel : subChannel; - - // TITLE - auto text = - set->key == "0" || set->text.isEmpty() ? "Twitch" : set->text; - channel.addMessage(makeTitleMessage(text)); - - // EMOTES + auto makeEmoteMessage(const EmoteMap &map) + { MessageBuilder builder; builder->flags.set(MessageFlag::Centered); builder->flags.set(MessageFlag::DisableCompactEmotes); - for (const auto &emote : set->emotes) { + for (const auto &emote : map) { builder - .emplace( - getApp()->emotes->twitch.getOrCreateEmote(emote.id, - emote.name), - MessageElementFlag::AlwaysShow) - ->setLink(Link(Link::InsertText, emote.name.string)); + .emplace(emote.second, + MessageElementFlag::AlwaysShow) + ->setLink(Link(Link::InsertText, emote.first.string)); } - channel.addMessage(builder.release()); + return builder.release(); + } + void addEmoteSets( + std::vector> sets, + Channel &globalChannel, Channel &subChannel) + { + for (const auto &set : sets) { + auto &channel = set->key == "0" ? globalChannel : subChannel; + + // TITLE + 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); + + for (const auto &emote : set->emotes) { + builder + .emplace( + getApp()->emotes->twitch.getOrCreateEmote(emote.id, + emote.name), + MessageElementFlag::AlwaysShow) + ->setLink(Link(Link::InsertText, emote.name.string)); + } + + channel.addMessage(builder.release()); + } } -} } // namespace EmotePopup::EmotePopup() @@ -122,8 +127,10 @@ void EmotePopup::loadChannel(ChannelPtr _channel) *globalChannel, *subChannel); // global - addEmotes(*globalChannel, *getApp()->emotes->bttv.global(), "BetterTTV"); - addEmotes(*globalChannel, *getApp()->emotes->ffz.global(), "FrankerFaceZ"); + addEmotes(*globalChannel, *twitchChannel->globalBttv().emotes(), + "BetterTTV"); + addEmotes(*globalChannel, *twitchChannel->globalFfz().emotes(), + "FrankerFaceZ"); // channel addEmotes(*channelChannel, *twitchChannel->bttvEmotes(), "BetterTTV"); diff --git a/src/widgets/dialogs/EmotePopup.hpp b/src/widgets/dialogs/EmotePopup.hpp index 98331efa7..b8ea9ebf9 100644 --- a/src/widgets/dialogs/EmotePopup.hpp +++ b/src/widgets/dialogs/EmotePopup.hpp @@ -1,13 +1,16 @@ #pragma once -#include "common/Channel.hpp" #include "widgets/BaseWindow.hpp" -#include "widgets/helper/ChannelView.hpp" #include namespace chatterino { +class Link; +class ChannelView; +class Channel; +using ChannelPtr = std::shared_ptr; + class EmotePopup : public BaseWindow { public: diff --git a/src/widgets/dialogs/LoginDialog.cpp b/src/widgets/dialogs/LoginDialog.cpp index 1b73f143f..35c18c7d6 100644 --- a/src/widgets/dialogs/LoginDialog.cpp +++ b/src/widgets/dialogs/LoginDialog.cpp @@ -1,12 +1,13 @@ #include "widgets/dialogs/LoginDialog.hpp" +#include "Application.hpp" #include "common/Common.hpp" #include "common/NetworkRequest.hpp" #include "controllers/accounts/AccountController.hpp" #include "providers/twitch/PartialTwitchUser.hpp" #ifdef USEWINSDK -#include +# include #endif #include @@ -20,54 +21,54 @@ namespace chatterino { namespace { -void LogInWithCredentials(const std::string &userID, - const std::string &username, - const std::string &clientID, - const std::string &oauthToken) -{ - QStringList errors; + void LogInWithCredentials(const std::string &userID, + const std::string &username, + const std::string &clientID, + const std::string &oauthToken) + { + QStringList errors; - if (userID.empty()) { - errors.append("Missing user ID"); + if (userID.empty()) { + errors.append("Missing user ID"); + } + if (username.empty()) { + errors.append("Missing username"); + } + if (clientID.empty()) { + errors.append("Missing Client ID"); + } + if (oauthToken.empty()) { + errors.append("Missing OAuth Token"); + } + + if (errors.length() > 0) { + QMessageBox messageBox; + messageBox.setIcon(QMessageBox::Critical); + messageBox.setText(errors.join("
")); + messageBox.setStandardButtons(QMessageBox::Ok); + messageBox.exec(); + return; + } + + // QMessageBox messageBox; + // messageBox.setIcon(QMessageBox::Information); + // messageBox.setText("Successfully logged in with user " + + // qS(username) + "!"); + pajlada::Settings::Setting::set( + "/accounts/uid" + userID + "/username", username); + pajlada::Settings::Setting::set( + "/accounts/uid" + userID + "/userID", userID); + pajlada::Settings::Setting::set( + "/accounts/uid" + userID + "/clientID", clientID); + pajlada::Settings::Setting::set( + "/accounts/uid" + userID + "/oauthToken", oauthToken); + + getApp()->accounts->twitch.reloadUsers(); + + // messageBox.exec(); + + getApp()->accounts->twitch.currentUsername = username; } - if (username.empty()) { - errors.append("Missing username"); - } - if (clientID.empty()) { - errors.append("Missing Client ID"); - } - if (oauthToken.empty()) { - errors.append("Missing OAuth Token"); - } - - if (errors.length() > 0) { - QMessageBox messageBox; - messageBox.setIcon(QMessageBox::Critical); - messageBox.setText(errors.join("
")); - messageBox.setStandardButtons(QMessageBox::Ok); - messageBox.exec(); - return; - } - - // QMessageBox messageBox; - // messageBox.setIcon(QMessageBox::Information); - // messageBox.setText("Successfully logged in with user " + - // qS(username) + "!"); - pajlada::Settings::Setting::set( - "/accounts/uid" + userID + "/username", username); - pajlada::Settings::Setting::set( - "/accounts/uid" + userID + "/userID", userID); - pajlada::Settings::Setting::set( - "/accounts/uid" + userID + "/clientID", clientID); - pajlada::Settings::Setting::set( - "/accounts/uid" + userID + "/oauthToken", oauthToken); - - getApp()->accounts->twitch.reloadUsers(); - - // messageBox.exec(); - - getApp()->accounts->twitch.currentUsername = username; -} } // namespace diff --git a/src/widgets/dialogs/LogsPopup.cpp b/src/widgets/dialogs/LogsPopup.cpp index 04aa78fb4..be799be53 100644 --- a/src/widgets/dialogs/LogsPopup.cpp +++ b/src/widgets/dialogs/LogsPopup.cpp @@ -1,6 +1,7 @@ #include "LogsPopup.hpp" #include "IrcMessage" +#include "common/Channel.hpp" #include "common/NetworkRequest.hpp" #include "providers/twitch/TwitchChannel.hpp" #include "providers/twitch/TwitchMessageBuilder.hpp" @@ -14,6 +15,7 @@ namespace chatterino { LogsPopup::LogsPopup() + : channel_(Channel::getEmpty()) { this->initLayout(); this->resize(400, 600); diff --git a/src/widgets/dialogs/LogsPopup.hpp b/src/widgets/dialogs/LogsPopup.hpp index 5ec39102e..395e1a384 100644 --- a/src/widgets/dialogs/LogsPopup.hpp +++ b/src/widgets/dialogs/LogsPopup.hpp @@ -1,6 +1,5 @@ #pragma once -#include "common/Channel.hpp" #include "widgets/BaseWindow.hpp" namespace chatterino { @@ -8,6 +7,12 @@ namespace chatterino { class Channel; class ChannelView; +class Channel; +using ChannelPtr = std::shared_ptr; + +struct Message; +using MessagePtr = std::shared_ptr; + class LogsPopup : public BaseWindow { public: @@ -17,7 +22,7 @@ public: private: ChannelView *channelView_ = nullptr; - ChannelPtr channel_ = Channel::getEmpty(); + ChannelPtr channel_; QString userName_; int roomID_; diff --git a/src/widgets/dialogs/NotificationPopup.cpp b/src/widgets/dialogs/NotificationPopup.cpp index 46ff7e8af..fb1822f40 100644 --- a/src/widgets/dialogs/NotificationPopup.cpp +++ b/src/widgets/dialogs/NotificationPopup.cpp @@ -1,5 +1,7 @@ #include "NotificationPopup.hpp" +#include "common/Channel.hpp" +#include "messages/Message.hpp" #include "widgets/helper/ChannelView.hpp" #include diff --git a/src/widgets/dialogs/NotificationPopup.hpp b/src/widgets/dialogs/NotificationPopup.hpp index 04a35b3a7..0f98a81bd 100644 --- a/src/widgets/dialogs/NotificationPopup.hpp +++ b/src/widgets/dialogs/NotificationPopup.hpp @@ -1,13 +1,17 @@ #pragma once -#include "common/Channel.hpp" -#include "messages/Message.hpp" #include "widgets/BaseWindow.hpp" namespace chatterino { class ChannelView; +class Channel; +using ChannelPtr = std::shared_ptr; + +struct Message; +using MessagePtr = std::shared_ptr; + class NotificationPopup : public BaseWindow { public: diff --git a/src/widgets/dialogs/SelectChannelDialog.cpp b/src/widgets/dialogs/SelectChannelDialog.cpp index 367b626b2..e760a823e 100644 --- a/src/widgets/dialogs/SelectChannelDialog.cpp +++ b/src/widgets/dialogs/SelectChannelDialog.cpp @@ -2,8 +2,10 @@ #include "Application.hpp" #include "providers/twitch/TwitchServer.hpp" +#include "singletons/Theme.hpp" #include "util/LayoutCreator.hpp" #include "widgets/Notebook.hpp" +#include "widgets/helper/NotebookTab.hpp" #include #include diff --git a/src/widgets/dialogs/SelectChannelDialog.hpp b/src/widgets/dialogs/SelectChannelDialog.hpp index 94f21595c..5d5890d9a 100644 --- a/src/widgets/dialogs/SelectChannelDialog.hpp +++ b/src/widgets/dialogs/SelectChannelDialog.hpp @@ -1,8 +1,8 @@ #pragma once +#include "Application.hpp" #include "common/Channel.hpp" #include "widgets/BaseWindow.hpp" -#include "widgets/Notebook.hpp" #include @@ -11,7 +11,9 @@ namespace chatterino { -class SelectChannelDialog : public BaseWindow +class Notebook; + +class SelectChannelDialog final : public BaseWindow { public: SelectChannelDialog(QWidget *parent = nullptr); diff --git a/src/widgets/dialogs/SettingsDialog.cpp b/src/widgets/dialogs/SettingsDialog.cpp index 8f0746ccb..1c89344f4 100644 --- a/src/widgets/dialogs/SettingsDialog.cpp +++ b/src/widgets/dialogs/SettingsDialog.cpp @@ -154,7 +154,7 @@ void SettingsDialog::showDialog(PreferredTab preferredTab) void SettingsDialog::refresh() { - getApp()->settings->saveSnapshot(); + getSettings()->saveSnapshot(); for (auto *tab : this->tabs_) { tab->getSettingsPage()->onShow(); @@ -199,7 +199,7 @@ void SettingsDialog::onCancelClicked() tab->getSettingsPage()->cancel(); } - getApp()->settings->restoreSnapshot(); + getSettings()->restoreSnapshot(); this->close(); } diff --git a/src/widgets/dialogs/UserInfoPopup.cpp b/src/widgets/dialogs/UserInfoPopup.cpp index e822da8b7..e7d3681ea 100644 --- a/src/widgets/dialogs/UserInfoPopup.cpp +++ b/src/widgets/dialogs/UserInfoPopup.cpp @@ -1,6 +1,7 @@ #include "UserInfoPopup.hpp" #include "Application.hpp" +#include "common/Channel.hpp" #include "common/NetworkRequest.hpp" #include "controllers/accounts/AccountController.hpp" #include "controllers/highlights/HighlightController.hpp" diff --git a/src/widgets/dialogs/UserInfoPopup.hpp b/src/widgets/dialogs/UserInfoPopup.hpp index 3dd13a464..59437d3da 100644 --- a/src/widgets/dialogs/UserInfoPopup.hpp +++ b/src/widgets/dialogs/UserInfoPopup.hpp @@ -1,6 +1,5 @@ #pragma once -#include "common/Channel.hpp" #include "widgets/BaseWindow.hpp" #include @@ -9,6 +8,8 @@ class QCheckBox; namespace chatterino { +class Channel; +using ChannelPtr = std::shared_ptr; class Label; class UserInfoPopup final : public BaseWindow diff --git a/src/widgets/helper/ChannelView.cpp b/src/widgets/helper/ChannelView.cpp index 3803f2a21..2afc42e75 100644 --- a/src/widgets/helper/ChannelView.cpp +++ b/src/widgets/helper/ChannelView.cpp @@ -1,18 +1,24 @@ #include "ChannelView.hpp" #include "Application.hpp" +#include "common/Common.hpp" #include "debug/Benchmark.hpp" #include "debug/Log.hpp" +#include "messages/Emote.hpp" #include "messages/LimitedQueueSnapshot.hpp" #include "messages/Message.hpp" +#include "messages/MessageElement.hpp" #include "messages/layouts/MessageLayout.hpp" +#include "messages/layouts/MessageLayoutElement.hpp" #include "providers/twitch/TwitchServer.hpp" #include "singletons/Settings.hpp" #include "singletons/Theme.hpp" #include "singletons/WindowManager.hpp" #include "util/DistanceBetweenPoints.hpp" +#include "widgets/Scrollbar.hpp" #include "widgets/TooltipWidget.hpp" #include "widgets/dialogs/UserInfoPopup.hpp" +#include "widgets/helper/EffectLabel.hpp" #include "widgets/splits/Split.hpp" #include @@ -33,67 +39,67 @@ namespace chatterino { namespace { -void addEmoteContextMenuItems(const Emote &emote, - MessageElementFlags creatorFlags, QMenu &menu) -{ - auto openAction = menu.addAction("Open"); - auto openMenu = new QMenu; - openAction->setMenu(openMenu); + void addEmoteContextMenuItems(const Emote &emote, + MessageElementFlags creatorFlags, QMenu &menu) + { + auto openAction = menu.addAction("Open"); + auto openMenu = new QMenu; + openAction->setMenu(openMenu); - auto copyAction = menu.addAction("Copy"); - auto copyMenu = new QMenu; - copyAction->setMenu(copyMenu); + auto copyAction = menu.addAction("Copy"); + auto copyMenu = new QMenu; + copyAction->setMenu(copyMenu); - // see if the QMenu actually gets destroyed - QObject::connect(openMenu, &QMenu::destroyed, [] { - QMessageBox(QMessageBox::Information, "xD", "the menu got deleted") - .exec(); - }); + // see if the QMenu actually gets destroyed + QObject::connect(openMenu, &QMenu::destroyed, [] { + QMessageBox(QMessageBox::Information, "xD", "the menu got deleted") + .exec(); + }); + + // Add copy and open links for 1x, 2x, 3x + auto addImageLink = [&](const ImagePtr &image, char scale) { + if (!image->isEmpty()) { + copyMenu->addAction( + QString(scale) + "x link", [url = image->url()] { + QApplication::clipboard()->setText(url.string); + }); + openMenu->addAction( + QString(scale) + "x link", [url = image->url()] { + QDesktopServices::openUrl(QUrl(url.string)); + }); + } + }; + + addImageLink(emote.images.getImage1(), '1'); + addImageLink(emote.images.getImage2(), '2'); + addImageLink(emote.images.getImage3(), '3'); + + // Copy and open emote page link + auto addPageLink = [&](const QString &name) { + copyMenu->addSeparator(); + openMenu->addSeparator(); - // Add copy and open links for 1x, 2x, 3x - auto addImageLink = [&](const ImagePtr &image, char scale) { - if (!image->isEmpty()) { copyMenu->addAction( - QString(scale) + "x link", [url = image->url()] { - QApplication::clipboard()->setText(url.string); + "Copy " + name + " emote link", [url = emote.homePage] { + QApplication::clipboard()->setText(url.string); // }); - openMenu->addAction(QString(scale) + "x link", - [url = image->url()] { - QDesktopServices::openUrl(QUrl(url.string)); - }); + openMenu->addAction( + "Open " + name + " emote link", [url = emote.homePage] { + QDesktopServices::openUrl(QUrl(url.string)); // + }); + }; + + if (creatorFlags.has(MessageElementFlag::BttvEmote)) { + addPageLink("BTTV"); + } else if (creatorFlags.has(MessageElementFlag::FfzEmote)) { + addPageLink("FFZ"); } - }; - - addImageLink(emote.images.getImage1(), '1'); - addImageLink(emote.images.getImage2(), '2'); - addImageLink(emote.images.getImage3(), '3'); - - // Copy and open emote page link - auto addPageLink = [&](const QString &name) { - copyMenu->addSeparator(); - openMenu->addSeparator(); - - copyMenu->addAction( - "Copy " + name + " emote link", [url = emote.homePage] { - QApplication::clipboard()->setText(url.string); // - }); - openMenu->addAction("Open " + name + " emote link", - [url = emote.homePage] { - QDesktopServices::openUrl(QUrl(url.string)); // - }); - }; - - if (creatorFlags.has(MessageElementFlag::BttvEmote)) { - addPageLink("BTTV"); - } else if (creatorFlags.has(MessageElementFlag::FfzEmote)) { - addPageLink("FFZ"); } -} } // namespace ChannelView::ChannelView(BaseWidget *parent) : BaseWidget(parent) - , scrollBar_(this) + , scrollBar_(new Scrollbar(this)) { this->setMouseTracking(true); @@ -124,27 +130,26 @@ void ChannelView::initializeLayout() QObject::connect(this->goToBottom_, &EffectLabel::clicked, this, [=] { QTimer::singleShot(180, [=] { - this->scrollBar_.scrollToBottom( - getApp() - ->settings->enableSmoothScrollingNewMessages.getValue()); + this->scrollBar_->scrollToBottom( + getSettings()->enableSmoothScrollingNewMessages.getValue()); }); }); } void ChannelView::initializeScrollbar() { - this->scrollBar_.getCurrentValueChanged().connect([this] { + this->scrollBar_->getCurrentValueChanged().connect([this] { this->actuallyLayoutMessages(true); this->goToBottom_->setVisible(this->enableScrollingToBottom_ && - this->scrollBar_.isVisible() && - !this->scrollBar_.isAtBottom()); + this->scrollBar_->isVisible() && + !this->scrollBar_->isAtBottom()); this->queueUpdate(); }); - this->scrollBar_.getDesiredValueChanged().connect([this] { - this->pausedByScrollingUp_ = !this->scrollBar_.isAtBottom(); + this->scrollBar_->getDesiredValueChanged().connect([this] { + this->pausedByScrollingUp_ = !this->scrollBar_->isAtBottom(); }); } @@ -156,7 +161,7 @@ void ChannelView::initializeSignals() this->update(); })); - getApp()->settings->showLastMessageIndicator.connect( + getSettings()->showLastMessageIndicator.connect( [this](auto, auto) { this->update(); }, this->connections_); connections_.push_back( @@ -207,12 +212,10 @@ void ChannelView::actuallyLayoutMessages(bool causedByScrollbar) { // BenchmarkGuard benchmark("layout"); - auto app = getApp(); - auto messagesSnapshot = this->getMessagesSnapshot(); if (messagesSnapshot.getLength() == 0) { - this->scrollBar_.setVisible(false); + this->scrollBar_->setVisible(false); return; } @@ -225,9 +228,9 @@ void ChannelView::actuallyLayoutMessages(bool causedByScrollbar) // The scrollbar was not visible // The scrollbar was visible and at the bottom this->showingLatestMessages_ = - this->scrollBar_.isAtBottom() || !this->scrollBar_.isVisible(); + this->scrollBar_->isAtBottom() || !this->scrollBar_->isVisible(); - size_t start = size_t(this->scrollBar_.getCurrentValue()); + size_t start = size_t(this->scrollBar_->getCurrentValue()); int layoutWidth = this->getLayoutWidth(); MessageElementFlags flags = this->getFlags(); @@ -235,7 +238,7 @@ void ChannelView::actuallyLayoutMessages(bool causedByScrollbar) // layout the visible messages in the view if (messagesSnapshot.getLength() > start) { int y = int(-(messagesSnapshot[start]->getHeight() * - (fmod(this->scrollBar_.getCurrentValue(), 1)))); + (fmod(this->scrollBar_->getCurrentValue(), 1)))); for (size_t i = start; i < messagesSnapshot.getLength(); ++i) { auto message = messagesSnapshot[i]; @@ -262,8 +265,9 @@ void ChannelView::actuallyLayoutMessages(bool causedByScrollbar) h -= message->getHeight(); if (h < 0) { - this->scrollBar_.setLargeChange((messagesSnapshot.getLength() - i) + - qreal(h) / message->getHeight()); + this->scrollBar_->setLargeChange( + (messagesSnapshot.getLength() - i) + + qreal(h) / message->getHeight()); // this->scrollBar.setDesiredValue(this->scrollBar.getDesiredValue()); showScrollbar = true; @@ -271,22 +275,22 @@ void ChannelView::actuallyLayoutMessages(bool causedByScrollbar) } } - this->scrollBar_.setVisible(showScrollbar); + this->scrollBar_->setVisible(showScrollbar); if (!showScrollbar && !causedByScrollbar) { - this->scrollBar_.setDesiredValue(0); + this->scrollBar_->setDesiredValue(0); } - this->scrollBar_.setMaximum(messagesSnapshot.getLength()); + this->scrollBar_->setMaximum(messagesSnapshot.getLength()); // If we were showing the latest messages and the scrollbar now wants to be // rendered, scroll to bottom if (this->enableScrollingToBottom_ && this->showingLatestMessages_ && showScrollbar) { if (!this->isPaused()) { - this->scrollBar_.scrollToBottom( + this->scrollBar_->scrollToBottom( // this->messageWasAdded && - app->settings->enableSmoothScrollingNewMessages.getValue()); + getSettings()->enableSmoothScrollingNewMessages.getValue()); } this->messageWasAdded_ = false; } @@ -309,7 +313,7 @@ void ChannelView::clearMessages() Scrollbar &ChannelView::getScrollBar() { - return this->scrollBar_; + return *this->scrollBar_; } QString ChannelView::getSelectedText() @@ -378,7 +382,7 @@ const boost::optional &ChannelView::getOverrideFlags() LimitedQueueSnapshot ChannelView::getMessagesSnapshot() { - if (!this->isPaused() /*|| this->scrollBar_.isVisible()*/) { + if (!this->isPaused() /*|| this->scrollBar_->isVisible()*/) { this->snapshot_ = this->messages.getSnapshot(); } @@ -413,10 +417,10 @@ void ChannelView::setChannel(ChannelPtr newChannel) if (this->messages.pushBack(MessageLayoutPtr(messageRef), deleted)) { // if (!this->isPaused()) { - if (this->scrollBar_.isAtBottom()) { - this->scrollBar_.scrollToBottom(); + if (this->scrollBar_->isAtBottom()) { + this->scrollBar_->scrollToBottom(); } else { - this->scrollBar_.offset(-1); + this->scrollBar_->offset(-1); } // } } @@ -431,7 +435,7 @@ void ChannelView::setChannel(ChannelPtr newChannel) } } - this->scrollBar_.addHighlight(message->getScrollBarHighlight()); + this->scrollBar_->addHighlight(message->getScrollBarHighlight()); this->messageWasAdded_ = true; this->layoutMessages(); @@ -449,10 +453,10 @@ void ChannelView::setChannel(ChannelPtr newChannel) if (!this->isPaused()) { if (this->messages.pushFront(messageRefs).size() > 0) { - if (this->scrollBar_.isAtBottom()) { - this->scrollBar_.scrollToBottom(); + if (this->scrollBar_->isAtBottom()) { + this->scrollBar_->scrollToBottom(); } else { - this->scrollBar_.offset(qreal(messages.size())); + this->scrollBar_->offset(qreal(messages.size())); } } } @@ -464,7 +468,7 @@ void ChannelView::setChannel(ChannelPtr newChannel) messages.at(i)->getScrollBarHighlight()); } - this->scrollBar_.addHighlightsAtStart(highlights); + this->scrollBar_->addHighlightsAtStart(highlights); this->messageWasAdded_ = true; this->layoutMessages(); @@ -503,7 +507,7 @@ void ChannelView::setChannel(ChannelPtr newChannel) newItem->flags.set(MessageLayoutFlag::AlternateBackground); } - this->scrollBar_.replaceHighlight( + this->scrollBar_->replaceHighlight( index, replacement->getScrollBarHighlight()); this->messages.replaceItem(message, newItem); @@ -563,12 +567,12 @@ void ChannelView::updateLastReadMessage() void ChannelView::resizeEvent(QResizeEvent *) { - this->scrollBar_.setGeometry(this->width() - this->scrollBar_.width(), 0, - this->scrollBar_.width(), this->height()); + this->scrollBar_->setGeometry(this->width() - this->scrollBar_->width(), 0, + this->scrollBar_->width(), this->height()); this->goToBottom_->setGeometry(0, this->height() - 32, this->width(), 32); - this->scrollBar_.raise(); + this->scrollBar_->raise(); this->layoutMessages(); @@ -625,9 +629,9 @@ bool ChannelView::isPaused() void ChannelView::updatePauseStatus() { if (this->isPaused()) { - this->scrollBar_.pauseHighlights(); + this->scrollBar_->pauseHighlights(); } else { - this->scrollBar_.unpauseHighlights(); + this->scrollBar_->unpauseHighlights(); } } @@ -647,18 +651,16 @@ void ChannelView::paintEvent(QPaintEvent * /*event*/) // such as the grey overlay when a message is disabled void ChannelView::drawMessages(QPainter &painter) { - auto app = getApp(); - auto messagesSnapshot = this->getMessagesSnapshot(); - size_t start = size_t(this->scrollBar_.getCurrentValue()); + size_t start = size_t(this->scrollBar_->getCurrentValue()); if (start >= messagesSnapshot.getLength()) { return; } int y = int(-(messagesSnapshot[start].get()->getHeight() * - (fmod(this->scrollBar_.getCurrentValue(), 1)))); + (fmod(this->scrollBar_->getCurrentValue(), 1)))); MessageLayout *end = nullptr; bool windowFocused = this->window() == QApplication::activeWindow(); @@ -667,7 +669,7 @@ void ChannelView::drawMessages(QPainter &painter) MessageLayout *layout = messagesSnapshot[i].get(); bool isLastMessage = false; - if (app->settings->showLastMessageIndicator) { + if (getSettings()->showLastMessageIndicator) { isLastMessage = this->lastReadMessage_.get() == layout; } @@ -729,12 +731,10 @@ void ChannelView::wheelEvent(QWheelEvent *event) return; } - if (this->scrollBar_.isVisible()) { - auto app = getApp(); + if (this->scrollBar_->isVisible()) { + float mouseMultiplier = getSettings()->mouseScrollMultiplier; - float mouseMultiplier = app->settings->mouseScrollMultiplier; - - qreal desired = this->scrollBar_.getDesiredValue(); + qreal desired = this->scrollBar_->getDesiredValue(); qreal delta = event->delta() * qreal(1.5) * mouseMultiplier; auto snapshot = this->getMessagesSnapshot(); @@ -792,7 +792,7 @@ void ChannelView::wheelEvent(QWheelEvent *event) } } - this->scrollBar_.setDesiredValue(desired, true); + this->scrollBar_->setDesiredValue(desired, true); } } @@ -817,9 +817,7 @@ void ChannelView::mouseMoveEvent(QMouseEvent *event) return; } - auto app = getApp(); - - if (app->settings->pauseChatHover.getValue()) { + if (getSettings()->pauseChatHover.getValue()) { this->pause(CHAT_HOVER_PAUSE_DURATION); } @@ -884,8 +882,6 @@ void ChannelView::mouseMoveEvent(QMouseEvent *event) void ChannelView::mousePressEvent(QMouseEvent *event) { - auto app = getApp(); - this->mouseDown.invoke(event); std::shared_ptr layout; @@ -922,7 +918,7 @@ void ChannelView::mousePressEvent(QMouseEvent *event) return; } - if (app->settings->linksDoubleClickOnly.getValue()) { + if (getSettings()->linksDoubleClickOnly.getValue()) { this->pause(200); } @@ -1027,7 +1023,7 @@ void ChannelView::handleMouseClick(QMouseEvent *event, } auto &link = hoveredElement->getLink(); - if (!getApp()->settings->linksDoubleClickOnly) { + if (!getSettings()->linksDoubleClickOnly) { this->handleLinkClick(event, link, layout); this->linkClicked.invoke(link); @@ -1092,17 +1088,18 @@ void ChannelView::addContextMenuItems( menu->addAction("Copy message", [layout] { QString copyString; - layout->addSelectionText(copyString); + layout->addSelectionText(copyString, 0, INT_MAX, + CopyMode::OnlyTextAndEmotes); QGuiApplication::clipboard()->setText(copyString); }); - // menu->addAction("Quote message", [layout] { - // QString copyString; - // layout->addSelectionText(copyString); + menu->addAction("Copy full message", [layout] { + QString copyString; + layout->addSelectionText(copyString); - // // insert into input - // }); + QGuiApplication::clipboard()->setText(copyString); + }); menu->popup(QCursor::pos()); menu->raise(); @@ -1112,9 +1109,7 @@ void ChannelView::addContextMenuItems( void ChannelView::mouseDoubleClickEvent(QMouseEvent *event) { - auto app = getApp(); - - if (app->settings->linksDoubleClickOnly) { + if (getSettings()->linksDoubleClickOnly) { std::shared_ptr layout; QPoint relativePos; int messageIndex; @@ -1192,14 +1187,14 @@ bool ChannelView::tryGetMessageAt(QPoint p, { auto messagesSnapshot = this->getMessagesSnapshot(); - size_t start = this->scrollBar_.getCurrentValue(); + size_t start = this->scrollBar_->getCurrentValue(); if (start >= messagesSnapshot.getLength()) { return false; } int y = -(messagesSnapshot[start]->getHeight() * - (fmod(this->scrollBar_.getCurrentValue(), 1))); + (fmod(this->scrollBar_->getCurrentValue(), 1))); for (size_t i = start; i < messagesSnapshot.getLength(); ++i) { auto message = messagesSnapshot[i]; @@ -1219,7 +1214,7 @@ bool ChannelView::tryGetMessageAt(QPoint p, int ChannelView::getLayoutWidth() const { - if (this->scrollBar_.isVisible()) + if (this->scrollBar_->isVisible()) return int(this->width() - 8 * this->getScale()); return this->width(); diff --git a/src/widgets/helper/ChannelView.hpp b/src/widgets/helper/ChannelView.hpp index cb8ad990f..46e51bebe 100644 --- a/src/widgets/helper/ChannelView.hpp +++ b/src/widgets/helper/ChannelView.hpp @@ -1,14 +1,10 @@ #pragma once -#include "common/Channel.hpp" -#include "messages/Image.hpp" +#include "common/FlagsEnum.hpp" +#include "messages/LimitedQueue.hpp" #include "messages/LimitedQueueSnapshot.hpp" -#include "messages/MessageElement.hpp" #include "messages/Selection.hpp" -#include "messages/layouts/MessageLayout.hpp" #include "widgets/BaseWidget.hpp" -#include "widgets/Scrollbar.hpp" -#include "widgets/helper/EffectLabel.hpp" #include #include @@ -20,8 +16,23 @@ #include namespace chatterino { +enum class HighlightState; -class ChannelView : public BaseWidget +class Channel; +using ChannelPtr = std::shared_ptr; + +class MessageLayout; +using MessageLayoutPtr = std::shared_ptr; + +enum class MessageElementFlag; +using MessageElementFlags = FlagsEnum; + +class Scrollbar; +class EffectLabel; +struct Link; +class MessageLayoutElement; + +class ChannelView final : public BaseWidget { Q_OBJECT @@ -121,7 +132,7 @@ private: ChannelPtr channel_; - Scrollbar scrollBar_; + Scrollbar *scrollBar_; EffectLabel *goToBottom_; // This variable can be used to decide whether or not we should render the diff --git a/src/widgets/helper/NotebookButton.cpp b/src/widgets/helper/NotebookButton.cpp index 926c42c24..c7ef9e1db 100644 --- a/src/widgets/helper/NotebookButton.cpp +++ b/src/widgets/helper/NotebookButton.cpp @@ -2,6 +2,7 @@ #include "singletons/Theme.hpp" #include "widgets/Notebook.hpp" #include "widgets/helper/Button.hpp" +#include "widgets/splits/Split.hpp" #include "widgets/splits/SplitContainer.hpp" #include diff --git a/src/widgets/helper/NotebookTab.cpp b/src/widgets/helper/NotebookTab.cpp index 5581f5e50..15388e34c 100644 --- a/src/widgets/helper/NotebookTab.cpp +++ b/src/widgets/helper/NotebookTab.cpp @@ -3,6 +3,7 @@ #include "Application.hpp" #include "common/Common.hpp" #include "debug/Log.hpp" +#include "singletons/Fonts.hpp" #include "singletons/Settings.hpp" #include "singletons/Theme.hpp" #include "util/Clamp.hpp" @@ -10,6 +11,7 @@ #include "widgets/Notebook.hpp" #include "widgets/dialogs/SettingsDialog.hpp" #include "widgets/dialogs/TextInputDialog.hpp" +#include "widgets/splits/SplitContainer.hpp" #include #include @@ -33,7 +35,7 @@ NotebookTab::NotebookTab(Notebook *notebook) this->positionChangedAnimation_.setEasingCurve( QEasingCurve(QEasingCurve::InCubic)); - app->settings->showTabCloseButton.connect( + getSettings()->showTabCloseButton.connect( boost::bind(&NotebookTab::hideTabXChanged, this, _1), this->managedConnections_); @@ -284,7 +286,7 @@ void NotebookTab::paintEvent(QPaintEvent *) painter.setPen(colors.text); // set area for text - int rectW = (!app->settings->showTabCloseButton ? 0 : int(16 * scale)); + int rectW = (!getSettings()->showTabCloseButton ? 0 : int(16 * scale)); QRect rect(0, 0, this->width() - rectW, height); // draw text @@ -339,7 +341,7 @@ void NotebookTab::paintEvent(QPaintEvent *) bool NotebookTab::hasXButton() { - return getApp()->settings->showTabCloseButton && + return getSettings()->showTabCloseButton && this->notebook_->getAllowUserTabManagement(); } @@ -432,7 +434,7 @@ void NotebookTab::mouseMoveEvent(QMouseEvent *event) { auto app = getApp(); - if (app->settings->showTabCloseButton && + if (getSettings()->showTabCloseButton && this->notebook_->getAllowUserTabManagement()) // { bool overX = this->getXRect().contains(event->pos()); diff --git a/src/widgets/helper/ResizingTextEdit.cpp b/src/widgets/helper/ResizingTextEdit.cpp index 731bed53b..1a06b1868 100644 --- a/src/widgets/helper/ResizingTextEdit.cpp +++ b/src/widgets/helper/ResizingTextEdit.cpp @@ -102,7 +102,7 @@ void ResizingTextEdit::keyPressEvent(QKeyEvent *event) // First type pressing tab after modifying a message, we refresh our // completion model this->completer_->setModel(completionModel); - completionModel->refresh(); + completionModel->refresh(currentCompletionPrefix); this->completionInProgress_ = true; this->completer_->setCompletionPrefix(currentCompletionPrefix); this->completer_->complete(); diff --git a/src/widgets/helper/ScrollbarHighlight.hpp b/src/widgets/helper/ScrollbarHighlight.hpp index b88cdae09..e34cf913b 100644 --- a/src/widgets/helper/ScrollbarHighlight.hpp +++ b/src/widgets/helper/ScrollbarHighlight.hpp @@ -1,7 +1,5 @@ #pragma once -#include - namespace chatterino { class ScrollbarHighlight diff --git a/src/widgets/helper/SearchPopup.cpp b/src/widgets/helper/SearchPopup.cpp index b67addb18..81439407a 100644 --- a/src/widgets/helper/SearchPopup.cpp +++ b/src/widgets/helper/SearchPopup.cpp @@ -6,6 +6,7 @@ #include #include "common/Channel.hpp" +#include "messages/Message.hpp" #include "widgets/helper/ChannelView.hpp" namespace chatterino { diff --git a/src/widgets/helper/SearchPopup.hpp b/src/widgets/helper/SearchPopup.hpp index 0a7065c55..1f2837fbe 100644 --- a/src/widgets/helper/SearchPopup.hpp +++ b/src/widgets/helper/SearchPopup.hpp @@ -1,7 +1,6 @@ #pragma once #include "messages/LimitedQueueSnapshot.hpp" -#include "messages/Message.hpp" #include "widgets/BaseWindow.hpp" #include @@ -13,6 +12,9 @@ namespace chatterino { class Channel; class ChannelView; +struct Message; +using MessagePtr = std::shared_ptr; + class SearchPopup : public BaseWindow { public: diff --git a/src/widgets/helper/TitlebarButton.cpp b/src/widgets/helper/TitlebarButton.cpp index c92e94ba6..d5ab4cc4a 100644 --- a/src/widgets/helper/TitlebarButton.cpp +++ b/src/widgets/helper/TitlebarButton.cpp @@ -9,12 +9,12 @@ TitleBarButton::TitleBarButton() { } -TitleBarButton::Style TitleBarButton::getButtonStyle() const +TitleBarButtonStyle TitleBarButton::getButtonStyle() const { return this->style_; } -void TitleBarButton::setButtonStyle(Style _style) +void TitleBarButton::setButtonStyle(TitleBarButtonStyle _style) { this->style_ = _style; this->update(); @@ -35,16 +35,16 @@ void TitleBarButton::paintEvent(QPaintEvent *event) painter.setRenderHint(QPainter::Antialiasing, false); switch (this->style_) { - case Minimize: { + case TitleBarButtonStyle::Minimize: { painter.fillRect(centerX - xD / 2, xD * 3 / 2, xD, 1, color); break; } - case Maximize: { + case TitleBarButtonStyle::Maximize: { painter.setPen(color); painter.drawRect(centerX - xD / 2, xD, xD - 1, xD - 1); break; } - case Unmaximize: { + case TitleBarButtonStyle::Unmaximize: { int xD2 = xD * 1 / 5; int xD3 = xD * 4 / 5; @@ -54,7 +54,7 @@ void TitleBarButton::paintEvent(QPaintEvent *event) painter.drawRect(centerX - xD / 2, xD + xD2, xD3, xD3); break; } - case Close: { + case TitleBarButtonStyle::Close: { QRect rect(centerX - xD / 2, xD, xD - 1, xD - 1); painter.setPen(QPen(color, 1)); @@ -62,7 +62,7 @@ void TitleBarButton::paintEvent(QPaintEvent *event) painter.drawLine(rect.topRight(), rect.bottomLeft()); break; } - case User: { + case TitleBarButtonStyle::User: { color = "#999"; painter.setRenderHint(QPainter::Antialiasing); @@ -88,7 +88,7 @@ void TitleBarButton::paintEvent(QPaintEvent *event) break; } - case Settings: { + case TitleBarButtonStyle::Settings: { color = "#999"; painter.setRenderHint(QPainter::Antialiasing); painter.setRenderHint(QPainter::HighQualityAntialiasing); diff --git a/src/widgets/helper/TitlebarButton.hpp b/src/widgets/helper/TitlebarButton.hpp index 33777660f..ed1261e9e 100644 --- a/src/widgets/helper/TitlebarButton.hpp +++ b/src/widgets/helper/TitlebarButton.hpp @@ -4,29 +4,29 @@ namespace chatterino { +enum class TitleBarButtonStyle { + None = 0, + Minimize = 1, + Maximize = 2, + Unmaximize = 4, + Close = 8, + User = 16, + Settings = 32 +}; + class TitleBarButton : public Button { public: - enum Style { - None = 0, - Minimize = 1, - Maximize = 2, - Unmaximize = 4, - Close = 8, - User = 16, - Settings = 32 - }; - TitleBarButton(); - Style getButtonStyle() const; - void setButtonStyle(Style style_); + TitleBarButtonStyle getButtonStyle() const; + void setButtonStyle(TitleBarButtonStyle style_); protected: void paintEvent(QPaintEvent *) override; private: - Style style_; + TitleBarButtonStyle style_; }; } // namespace chatterino diff --git a/src/widgets/settingspages/CommandPage.cpp b/src/widgets/settingspages/CommandPage.cpp index bb3a60c59..b06988a57 100644 --- a/src/widgets/settingspages/CommandPage.cpp +++ b/src/widgets/settingspages/CommandPage.cpp @@ -47,7 +47,7 @@ CommandPage::CommandPage() layout.append( this->createCheckBox("Also match the trigger at the end of the message", - app->settings->allowCommandsAtEnd)); + getSettings()->allowCommandsAtEnd)); QLabel *text = layout.emplace(TEXT).getElement(); text->setWordWrap(true); diff --git a/src/widgets/settingspages/ExternalToolsPage.cpp b/src/widgets/settingspages/ExternalToolsPage.cpp index 0f47e37a1..49204d9cb 100644 --- a/src/widgets/settingspages/ExternalToolsPage.cpp +++ b/src/widgets/settingspages/ExternalToolsPage.cpp @@ -9,15 +9,13 @@ "Choose", "Source", "High", "Medium", "Low", "Audio only" namespace chatterino { - namespace { - -QString CreateLink(const QString &url, const QString &name) -{ - return QString("" + - name + ""); -} - + QString createLink(const QString &url, const QString &name) + { + return QString("" + name + + ""); + } } // namespace ExternalToolsPage::ExternalToolsPage() @@ -41,8 +39,8 @@ ExternalToolsPage::ExternalToolsPage() description->setStyleSheet("color: #bbb"); auto links = new QLabel( - CreateLink("https://streamlink.github.io/", "Website") + " " + - CreateLink( + createLink("https://streamlink.github.io/", "Website") + " " + + createLink( "https://github.com/streamlink/streamlink/releases/latest", "Download")); links->setTextFormat(Qt::RichText); @@ -57,22 +55,22 @@ ExternalToolsPage::ExternalToolsPage() auto customPathCb = this->createCheckBox("Use custom path (Enable if using " "non-standard streamlink installation path)", - app->settings->streamlinkUseCustomPath); + getSettings()->streamlinkUseCustomPath); groupLayout->setWidget(2, QFormLayout::SpanningRole, customPathCb); - auto customPath = this->createLineEdit(app->settings->streamlinkPath); + auto customPath = this->createLineEdit(getSettings()->streamlinkPath); customPath->setPlaceholderText( "Path to folder where Streamlink executable can be found"); groupLayout->addRow("Custom streamlink path:", customPath); groupLayout->addRow( "Preferred quality:", this->createComboBox({STREAMLINK_QUALITY}, - app->settings->preferredQuality)); + getSettings()->preferredQuality)); groupLayout->addRow( "Additional options:", - this->createLineEdit(app->settings->streamlinkOpts)); + this->createLineEdit(getSettings()->streamlinkOpts)); - app->settings->streamlinkUseCustomPath.connect( + getSettings()->streamlinkUseCustomPath.connect( [=](const auto &value, auto) { customPath->setEnabled(value); // }, diff --git a/src/widgets/settingspages/FeelPage.cpp b/src/widgets/settingspages/FeelPage.cpp index d20c859c7..78b41c45d 100644 --- a/src/widgets/settingspages/FeelPage.cpp +++ b/src/widgets/settingspages/FeelPage.cpp @@ -20,7 +20,6 @@ namespace chatterino { FeelPage::FeelPage() : SettingsPage("Feel", ":/settings/behave.svg") { - auto app = getApp(); LayoutCreator layoutCreator(this); auto layout = layoutCreator.setLayoutType(); @@ -37,20 +36,20 @@ FeelPage::FeelPage() form->addRow( "", this->createCheckBox( "Show which users joined the channel (up to 1000 chatters)", - app->settings->showJoins)); + getSettings()->showJoins)); form->addRow( "", this->createCheckBox( "Show which users parted the channel (up to 1000 chatters)", - app->settings->showParts)); + getSettings()->showParts)); form->addRow("Pause chat:", this->createCheckBox(PAUSE_HOVERING, - app->settings->pauseChatHover)); + getSettings()->pauseChatHover)); form->addRow("Mouse scroll speed:", this->createMouseScrollSlider()); form->addRow("Links:", this->createCheckBox("Open links only on double click", - app->settings->linksDoubleClickOnly)); + getSettings()->linksDoubleClickOnly)); } layout->addSpacing(16); @@ -61,11 +60,11 @@ FeelPage::FeelPage() groupLayout->addRow( LIMIT_CHATTERS_FOR_SMALLER_STREAMERS, this->createCheckBox( - "", app->settings->onlyFetchChattersForSmallerStreamers)); + "", getSettings()->onlyFetchChattersForSmallerStreamers)); groupLayout->addRow( "What viewer count counts as a \"smaller streamer\"", - this->createSpinBox(app->settings->smallStreamerLimit, 10, 50000)); + this->createSpinBox(getSettings()->smallStreamerLimit, 10, 50000)); } { @@ -73,7 +72,7 @@ FeelPage::FeelPage() auto groupLayout = group.setLayoutType(); groupLayout.append(this->createCheckBox("Show whispers inline", - app->settings->inlineWhispers)); + getSettings()->inlineWhispers)); } layout->addStretch(1); @@ -81,17 +80,16 @@ FeelPage::FeelPage() QSlider *FeelPage::createMouseScrollSlider() { - auto app = getApp(); auto slider = new QSlider(Qt::Horizontal); - float currentValue = app->settings->mouseScrollMultiplier; + float currentValue = getSettings()->mouseScrollMultiplier; int sliderValue = int(((currentValue - 0.1f) / 2.f) * 99.f); slider->setValue(sliderValue); QObject::connect(slider, &QSlider::valueChanged, [=](int newValue) { float mul = static_cast(newValue) / 99.f; float newSliderValue = (mul * 2.1f) + 0.1f; - app->settings->mouseScrollMultiplier = newSliderValue; + getSettings()->mouseScrollMultiplier = newSliderValue; }); return slider; diff --git a/src/widgets/settingspages/HighlightingPage.cpp b/src/widgets/settingspages/HighlightingPage.cpp index 455c0d377..be394b3c2 100644 --- a/src/widgets/settingspages/HighlightingPage.cpp +++ b/src/widgets/settingspages/HighlightingPage.cpp @@ -39,7 +39,7 @@ HighlightingPage::HighlightingPage() { // GENERAL // layout.append(this->createCheckBox(ENABLE_HIGHLIGHTS, - // app->settings->enableHighlights)); + // getSettings()->enableHighlights)); // TABS auto tabs = layout.emplace(); @@ -135,7 +135,7 @@ HighlightingPage::HighlightingPage() auto customSound = layout.emplace().withoutMargin(); { customSound.append(this->createCheckBox( - "Custom sound", app->settings->customHighlightSound)); + "Custom sound", getSettings()->customHighlightSound)); auto selectFile = customSound.emplace("Select custom sound file"); QObject::connect(selectFile.getElement(), &QPushButton::clicked, @@ -143,12 +143,12 @@ HighlightingPage::HighlightingPage() auto fileName = QFileDialog::getOpenFileName( this, tr("Open Sound"), "", tr("Audio Files (*.mp3 *.wav)")); - app->settings->pathHighlightSound = fileName; + getSettings()->pathHighlightSound = fileName; }); } layout.append(createCheckBox(ALWAYS_PLAY, - app->settings->highlightAlwaysPlaySound)); + getSettings()->highlightAlwaysPlaySound)); } // ---- misc diff --git a/src/widgets/settingspages/IgnoresPage.cpp b/src/widgets/settingspages/IgnoresPage.cpp index a8465a619..dd08a19a6 100644 --- a/src/widgets/settingspages/IgnoresPage.cpp +++ b/src/widgets/settingspages/IgnoresPage.cpp @@ -4,6 +4,7 @@ #include "controllers/accounts/AccountController.hpp" #include "controllers/ignores/IgnoreController.hpp" #include "controllers/ignores/IgnoreModel.hpp" +#include "providers/twitch/TwitchAccount.hpp" #include "singletons/Settings.hpp" #include "util/LayoutCreator.hpp" #include "widgets/helper/EditableModelView.hpp" @@ -63,7 +64,7 @@ void addPhrasesTab(LayoutCreator layout) void addUsersTab(IgnoresPage &page, LayoutCreator users, QStringListModel &userModel) { users.append(page.createCheckBox("Enable twitch ignored users", - getApp()->settings->enableTwitchIgnoredUsers)); + getSettings()->enableTwitchIgnoredUsers)); auto anyways = users.emplace().withoutMargin(); { diff --git a/src/widgets/settingspages/LogsPage.cpp b/src/widgets/settingspages/LogsPage.cpp index 8cf1fbcc1..2be6c2144 100644 --- a/src/widgets/settingspages/LogsPage.cpp +++ b/src/widgets/settingspages/LogsPage.cpp @@ -33,7 +33,7 @@ // LayoutCreator layoutCreator(this); // auto layout = layoutCreator.emplace().withoutMargin(); -// auto logPath = app->paths->logsFolderPath; +// auto logPath = getPaths()->logsFolderPath; // auto created = layout.emplace(); // created->setText("Logs are saved to " + CreateLink(logPath, true)); @@ -43,7 +43,7 @@ // Qt::LinksAccessibleByKeyboard); // created->setOpenExternalLinks(true); // layout.append(this->createCheckBox("Enable logging", -// app->settings->enableLogging)); +// getSettings()->enableLogging)); // layout->addStretch(1); //} diff --git a/src/widgets/settingspages/LookPage.cpp b/src/widgets/settingspages/LookPage.cpp index 8cdcfd5f9..eb2a9ff1c 100644 --- a/src/widgets/settingspages/LookPage.cpp +++ b/src/widgets/settingspages/LookPage.cpp @@ -1,10 +1,14 @@ #include "LookPage.hpp" #include "Application.hpp" +#include "messages/Image.hpp" #include "messages/MessageBuilder.hpp" +#include "singletons/Resources.hpp" +#include "singletons/Theme.hpp" #include "singletons/WindowManager.hpp" #include "util/LayoutCreator.hpp" #include "util/RemoveScrollAreaBackground.hpp" +#include "widgets/helper/ChannelView.hpp" #include "widgets/helper/Line.hpp" #include @@ -27,9 +31,9 @@ // clang-format on #ifdef USEWINSDK -#define WINDOW_TOPMOST "Window always on top" +# define WINDOW_TOPMOST "Window always on top" #else -#define WINDOW_TOPMOST "Window always on top (requires restart)" +# define WINDOW_TOPMOST "Window always on top (requires restart)" #endif #define INPUT_EMPTY "Show input box when empty" #define LAST_MSG "Mark the last message you read" @@ -204,16 +208,19 @@ void LookPage::addEmoteTab(LayoutCreator layout) /* emotes.append( this->createCheckBox("Enable Twitch emotes", - app->settings->enableTwitchEmotes)); + getSettings()->enableTwitchEmotes)); emotes.append(this->createCheckBox("Enable BetterTTV emotes for Twitch", - app->settings->enableBttvEmotes)); + getSettings()->enableBttvEmotes)); emotes.append(this->createCheckBox("Enable FrankerFaceZ emotes for Twitch", - app->settings->enableFfzEmotes)); + getSettings()->enableFfzEmotes)); emotes.append(this->createCheckBox("Enable emojis", - app->settings->enableEmojis)); + getSettings()->enableEmojis)); */ layout.append( this->createCheckBox("Animations", getSettings()->enableGifAnimations)); + layout.append( + this->createCheckBox("Animations only when chatterino has focus", + getSettings()->enableAnimationsWhenFocused)); auto scaleBox = layout.emplace().withoutMargin(); { @@ -281,7 +288,7 @@ void LookPage::addLastReadMessageIndicatorPatternSelector( combo->addItems({"Dotted line", "Solid line"}); const auto currentIndex = []() -> int { - switch (getApp()->settings->lastMessagePattern.getValue()) { + switch (getSettings()->lastMessagePattern.getValue()) { case Qt::SolidLine: return 1; case Qt::VerPattern: @@ -416,7 +423,7 @@ QLayout *LookPage::createFontChanger() button->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Policy::Fixed); QObject::connect(button, &QPushButton::clicked, [=]() { - QFontDialog dialog(app->fonts->getFont(Fonts::ChatMedium, 1.)); + QFontDialog dialog(app->fonts->getFont(FontStyle::ChatMedium, 1.)); dialog.setWindowFlag(Qt::WindowStaysOnTopHint); diff --git a/src/widgets/settingspages/ModerationPage.cpp b/src/widgets/settingspages/ModerationPage.cpp index fdb6df89f..89d6b2411 100644 --- a/src/widgets/settingspages/ModerationPage.cpp +++ b/src/widgets/settingspages/ModerationPage.cpp @@ -25,7 +25,7 @@ namespace chatterino { -inline QString CreateLink(const QString &url, bool file = false) +inline QString createLink(const QString &url, bool file = false) { if (file) { return QString("settings->logPath == "") { - logPathDirectory = app->paths->messageLogDirectory; + if (getSettings()->logPath == "") { + logPathDirectory = getPaths()->messageLogDirectory; } else { - logPathDirectory = app->settings->logPath; + logPathDirectory = getSettings()->logPath; } qint64 logsSize = dirSize(logPathDirectory); QString logsSizeLabel = "Your logs currently take up "; @@ -101,12 +100,12 @@ ModerationPage::ModerationPage() QtConcurrent::run([] { return fetchLogDirectorySize(); })); // Logs (copied from LoggingMananger) - app->settings->logPath.connect( + getSettings()->logPath.connect( [app, logsPathLabel](const QString &logPath, auto) mutable { QString pathOriginal; if (logPath == "") { - pathOriginal = app->paths->messageLogDirectory; + pathOriginal = getPaths()->messageLogDirectory; } else { pathOriginal = logPath; } @@ -136,7 +135,7 @@ ModerationPage::ModerationPage() Qt::LinksAccessibleByKeyboard); logsPathLabel->setOpenExternalLinks(true); logs.append(this->createCheckBox("Enable logging", - app->settings->enableLogging)); + getSettings()->enableLogging)); logs->addStretch(1); auto selectDir = logs.emplace("Set custom logpath"); @@ -145,10 +144,9 @@ ModerationPage::ModerationPage() QObject::connect( selectDir.getElement(), &QPushButton::clicked, this, [this, logsPathSizeLabel]() mutable { - auto app = getApp(); auto dirName = QFileDialog::getExistingDirectory(this); - app->settings->logPath = dirName; + getSettings()->logPath = dirName; // Refresh: Show how big (size-wise) the logs are logsPathSizeLabel->setText( @@ -159,8 +157,7 @@ ModerationPage::ModerationPage() auto resetDir = logs.emplace("Reset logpath"); QObject::connect(resetDir.getElement(), &QPushButton::clicked, this, [logsPathSizeLabel]() mutable { - auto app = getApp(); - app->settings->logPath = ""; + getSettings()->logPath = ""; // Refresh: Show how big (size-wise) the logs are logsPathSizeLabel->setText(QtConcurrent::run( @@ -183,7 +180,7 @@ ModerationPage::ModerationPage() // form->addRow("Action on timed out messages // (unimplemented):", // this->createComboBox({"Disable", "Hide"}, - // app->settings->timeoutAction)); + // getSettings()->timeoutAction)); // } EditableModelView *view = diff --git a/src/widgets/settingspages/SpecialChannelsPage.cpp b/src/widgets/settingspages/SpecialChannelsPage.cpp index cdd016dea..5ccb904c0 100644 --- a/src/widgets/settingspages/SpecialChannelsPage.cpp +++ b/src/widgets/settingspages/SpecialChannelsPage.cpp @@ -13,8 +13,6 @@ namespace chatterino { SpecialChannelsPage::SpecialChannelsPage() : SettingsPage("Special channels", "") { - auto app = getApp(); - LayoutCreator layoutCreator(this); auto layout = layoutCreator.setLayoutType(); diff --git a/src/widgets/splits/Split.cpp b/src/widgets/splits/Split.cpp index 38b390697..01a2faaad 100644 --- a/src/widgets/splits/Split.cpp +++ b/src/widgets/splits/Split.cpp @@ -1,8 +1,8 @@ #include "widgets/splits/Split.hpp" -#include "Application.hpp" #include "common/Common.hpp" #include "common/NetworkRequest.hpp" +#include "debug/Log.hpp" #include "providers/twitch/EmoteValue.hpp" #include "providers/twitch/TwitchChannel.hpp" #include "providers/twitch/TwitchMessageBuilder.hpp" @@ -11,15 +11,20 @@ #include "singletons/Theme.hpp" #include "singletons/WindowManager.hpp" #include "util/StreamLink.hpp" +#include "widgets/Notebook.hpp" #include "widgets/Window.hpp" #include "widgets/dialogs/QualityPopup.hpp" #include "widgets/dialogs/SelectChannelDialog.hpp" #include "widgets/dialogs/TextInputDialog.hpp" #include "widgets/dialogs/UserInfoPopup.hpp" +#include "widgets/helper/ChannelView.hpp" #include "widgets/helper/DebugPopup.hpp" +#include "widgets/helper/ResizingTextEdit.hpp" #include "widgets/helper/SearchPopup.hpp" #include "widgets/helper/Shortcut.hpp" #include "widgets/splits/SplitContainer.hpp" +#include "widgets/splits/SplitHeader.hpp" +#include "widgets/splits/SplitInput.hpp" #include "widgets/splits/SplitOverlay.hpp" #include @@ -37,7 +42,6 @@ #include namespace chatterino { - pajlada::Signals::Signal Split::modifierStatusChanged; Qt::KeyboardModifiers Split::modifierStatus = Qt::NoModifier; @@ -51,20 +55,20 @@ Split::Split(QWidget *parent) : BaseWidget(parent) , container_(nullptr) , channel_(Channel::getEmpty()) - , vbox_(this) - , header_(this) - , view_(this) - , input_(this) + , vbox_(new QVBoxLayout(this)) + , header_(new SplitHeader(this)) + , view_(new ChannelView(this)) + , input_(new SplitInput(this)) , overlay_(new SplitOverlay(this)) { this->setMouseTracking(true); - this->vbox_.setSpacing(0); - this->vbox_.setMargin(1); + this->vbox_->setSpacing(0); + this->vbox_->setMargin(1); - this->vbox_.addWidget(&this->header_); - this->vbox_.addWidget(&this->view_, 1); - this->vbox_.addWidget(&this->input_); + this->vbox_->addWidget(this->header_); + this->vbox_->addWidget(this->view_, 1); + this->vbox_->addWidget(this->input_); // Initialize chat widget-wide hotkeys // CTRL+W: Close Split @@ -89,41 +93,41 @@ Split::Split(QWidget *parent) // CreateShortcut(this, "ALT+SHIFT+UP", &Split::doIncFlexY); // CreateShortcut(this, "ALT+SHIFT+DOWN", &Split::doDecFlexY); - this->input_.ui_.textEdit->installEventFilter(parent); + this->input_->ui_.textEdit->installEventFilter(parent); - this->view_.mouseDown.connect([this](QMouseEvent *) { + this->view_->mouseDown.connect([this](QMouseEvent *) { // this->giveFocus(Qt::MouseFocusReason); }); - this->view_.selectionChanged.connect([this]() { - if (view_.hasSelection()) { - this->input_.clearSelection(); + this->view_->selectionChanged.connect([this]() { + if (view_->hasSelection()) { + this->input_->clearSelection(); } }); - this->input_.textChanged.connect([=](const QString &newText) { + this->input_->textChanged.connect([=](const QString &newText) { if (getSettings()->showEmptyInput) { return; } if (newText.length() == 0) { - this->input_.hide(); - } else if (this->input_.isHidden()) { - this->input_.show(); + this->input_->hide(); + } else if (this->input_->isHidden()) { + this->input_->show(); } }); getSettings()->showEmptyInput.connect( [this](const bool &showEmptyInput, auto) { - if (!showEmptyInput && this->input_.getInputText().length() == 0) { - this->input_.hide(); + if (!showEmptyInput && this->input_->getInputText().length() == 0) { + this->input_->hide(); } else { - this->input_.show(); + this->input_->show(); } }, this->managedConnections_); - this->header_.updateModerationModeIcon(); + this->header_->updateModerationModeIcon(); this->overlay_->hide(); this->setSizePolicy(QSizePolicy::MinimumExpanding, @@ -139,9 +143,9 @@ Split::Split(QWidget *parent) } }); - this->input_.ui_.textEdit->focused.connect( + this->input_->ui_.textEdit->focused.connect( [this] { this->focused.invoke(); }); - this->input_.ui_.textEdit->focusLost.connect( + this->input_->ui_.textEdit->focusLost.connect( [this] { this->focusLost.invoke(); }); } @@ -155,7 +159,7 @@ Split::~Split() ChannelView &Split::getChannelView() { - return this->view_; + return *this->view_; } SplitContainer *Split::getContainer() @@ -187,7 +191,7 @@ void Split::setChannel(IndirectChannel newChannel) { this->channel_ = newChannel; - this->view_.setChannel(newChannel.get()); + this->view_->setChannel(newChannel.get()); this->usermodeChangedConnection_.disconnect(); this->roomModeChangedConnection_.disconnect(); @@ -197,12 +201,12 @@ void Split::setChannel(IndirectChannel newChannel) if (tc != nullptr) { this->usermodeChangedConnection_ = tc->userStateChanged.connect([this] { - this->header_.updateModerationModeIcon(); - this->header_.updateRoomModes(); + this->header_->updateModerationModeIcon(); + this->header_->updateRoomModes(); }); this->roomModeChangedConnection_ = tc->roomModesChanged.connect( - [this] { this->header_.updateRoomModes(); }); + [this] { this->header_->updateRoomModes(); }); } this->indirectChannelChangedConnection_ = @@ -210,9 +214,9 @@ void Split::setChannel(IndirectChannel newChannel) QTimer::singleShot(0, [this] { this->setChannel(this->channel_); }); }); - this->header_.updateModerationModeIcon(); - this->header_.updateChannelText(); - this->header_.updateRoomModes(); + this->header_->updateModerationModeIcon(); + this->header_->updateChannelText(); + this->header_->updateRoomModes(); this->channelChanged.invoke(); } @@ -221,8 +225,8 @@ void Split::setModerationMode(bool value) { if (value != this->moderationMode_) { this->moderationMode_ = value; - this->header_.updateModerationModeIcon(); - this->view_.layoutMessages(); + this->header_->updateModerationModeIcon(); + this->view_->layoutMessages(); } } @@ -233,7 +237,7 @@ bool Split::getModerationMode() const void Split::insertTextToInput(const QString &text) { - this->input_.insertText(text); + this->input_->insertText(text); } void Split::showChangeChannelPopup(const char *dialogTitle, bool empty, @@ -267,27 +271,27 @@ void Split::showChangeChannelPopup(const char *dialogTitle, bool empty, void Split::layoutMessages() { - this->view_.layoutMessages(); + this->view_->layoutMessages(); } void Split::updateGifEmotes() { - this->view_.queueUpdate(); + this->view_->queueUpdate(); } void Split::updateLastReadMessage() { - this->view_.updateLastReadMessage(); + this->view_->updateLastReadMessage(); } void Split::giveFocus(Qt::FocusReason reason) { - this->input_.ui_.textEdit->setFocus(reason); + this->input_->ui_.textEdit->setFocus(reason); } bool Split::hasFocus() const { - return this->input_.ui_.textEdit->hasFocus(); + return this->input_->ui_.textEdit->hasFocus(); } void Split::paintEvent(QPaintEvent *) @@ -305,13 +309,13 @@ void Split::mouseMoveEvent(QMouseEvent *event) void Split::keyPressEvent(QKeyEvent *event) { - this->view_.unsetCursor(); + this->view_->unsetCursor(); this->handleModifiers(QGuiApplication::queryKeyboardModifiers()); } void Split::keyReleaseEvent(QKeyEvent *event) { - this->view_.unsetCursor(); + this->view_->unsetCursor(); this->handleModifiers(QGuiApplication::queryKeyboardModifiers()); } @@ -389,7 +393,7 @@ void Split::changeChannel() void Split::popup() { auto app = getApp(); - Window &window = app->windows->createWindow(Window::Type::Popup); + Window &window = app->windows->createWindow(WindowType::Popup); Split *split = new Split(static_cast( window.getNotebook().getOrAddSelectedPage())); @@ -402,7 +406,7 @@ void Split::popup() void Split::clear() { - this->view_.clearMessages(); + this->view_->clearMessages(); } void Split::openInBrowser() @@ -442,8 +446,8 @@ void Split::showViewerList() QDockWidget::DockWidgetFloatable); viewerDock->resize( 0.5 * this->width(), - this->height() - this->header_.height() - this->input_.height()); - viewerDock->move(0, this->header_.height()); + this->height() - this->header_->height() - this->input_->height()); + viewerDock->move(0, this->header_->height()); auto multiWidget = new QWidget(viewerDock); auto dockVbox = new QVBoxLayout(viewerDock); @@ -543,7 +547,7 @@ void Split::showUserInfoPopup(const UserName &user) void Split::copyToClipboard() { - QApplication::clipboard()->setText(this->view_.getSelectedText()); + QApplication::clipboard()->setText(this->view_->getSelectedText()); } void Split::showSearch() diff --git a/src/widgets/splits/Split.hpp b/src/widgets/splits/Split.hpp index ca185dbdb..8c4ebf84a 100644 --- a/src/widgets/splits/Split.hpp +++ b/src/widgets/splits/Split.hpp @@ -1,17 +1,9 @@ #pragma once +#include "common/Aliases.hpp" #include "common/Channel.hpp" #include "common/NullablePtr.hpp" -#include "messages/LimitedQueueSnapshot.hpp" -#include "messages/MessageElement.hpp" -#include "messages/layouts/MessageLayout.hpp" -#include "messages/layouts/MessageLayoutElement.hpp" -#include "util/RapidJsonSerializeQString.hpp" #include "widgets/BaseWidget.hpp" -#include "widgets/helper/ChannelView.hpp" -#include "widgets/helper/EffectLabel.hpp" -#include "widgets/splits/SplitHeader.hpp" -#include "widgets/splits/SplitInput.hpp" #include #include @@ -20,6 +12,9 @@ namespace chatterino { +class ChannelView; +class SplitHeader; +class SplitInput; class SplitContainer; class SplitOverlay; class SelectChannelDialog; @@ -103,10 +98,10 @@ private: SplitContainer *container_; IndirectChannel channel_; - QVBoxLayout vbox_; - SplitHeader header_; - ChannelView view_; - SplitInput input_; + QVBoxLayout *vbox_; + SplitHeader *header_; + ChannelView *view_; + SplitInput *input_; SplitOverlay *overlay_; NullablePtr selectChannelDialog_; diff --git a/src/widgets/splits/SplitContainer.cpp b/src/widgets/splits/SplitContainer.cpp index ecbadc21d..cac6f2dfb 100644 --- a/src/widgets/splits/SplitContainer.cpp +++ b/src/widgets/splits/SplitContainer.cpp @@ -7,6 +7,7 @@ #include "util/Helpers.hpp" #include "util/LayoutCreator.hpp" #include "widgets/Notebook.hpp" +#include "widgets/helper/ChannelView.hpp" #include "widgets/helper/NotebookTab.hpp" #include "widgets/splits/Split.hpp" diff --git a/src/widgets/splits/SplitContainer.hpp b/src/widgets/splits/SplitContainer.hpp index 7bd2ced1a..1f0a2e701 100644 --- a/src/widgets/splits/SplitContainer.hpp +++ b/src/widgets/splits/SplitContainer.hpp @@ -1,8 +1,6 @@ #pragma once #include "widgets/BaseWidget.hpp" -#include "widgets/helper/NotebookTab.hpp" -#include "widgets/splits/Split.hpp" #include #include @@ -13,21 +11,24 @@ #include #include -#include - #include #include +#include class QJsonObject; namespace chatterino { +class Split; +class NotebookTab; +class Notebook; + // // Note: This class is a spaghetti container. There is a lot of spaghetti code // inside but it doesn't expose any of it publicly. // -class SplitContainer : public BaseWidget, pajlada::Signals::SignalHolder +class SplitContainer final : public BaseWidget, pajlada::Signals::SignalHolder { Q_OBJECT diff --git a/src/widgets/splits/SplitHeader.cpp b/src/widgets/splits/SplitHeader.cpp index 882f0b9eb..611bbaace 100644 --- a/src/widgets/splits/SplitHeader.cpp +++ b/src/widgets/splits/SplitHeader.cpp @@ -5,17 +5,20 @@ #include "providers/twitch/TwitchChannel.hpp" #include "providers/twitch/TwitchServer.hpp" #include "singletons/Resources.hpp" +#include "singletons/Settings.hpp" #include "singletons/Theme.hpp" #include "util/LayoutCreator.hpp" #include "util/LayoutHelper.hpp" #include "widgets/Label.hpp" #include "widgets/TooltipWidget.hpp" +#include "widgets/helper/EffectLabel.hpp" #include "widgets/splits/Split.hpp" #include "widgets/splits/SplitContainer.hpp" #include #include #include +#include #include #include #include @@ -23,86 +26,87 @@ #include #ifdef USEWEBENGINE -#include "widgets/StreamView.hpp" +# include "widgets/StreamView.hpp" #endif namespace chatterino { namespace { -auto formatRoomMode(TwitchChannel &channel) -> QString -{ - QString text; - + auto formatRoomMode(TwitchChannel &channel) -> QString { - auto modes = channel.accessRoomModes(); + QString text; - if (modes->r9k) text += "r9k, "; - if (modes->slowMode) - text += QString("slow(%1), ").arg(QString::number(modes->slowMode)); - if (modes->emoteOnly) text += "emote, "; - if (modes->submode) text += "sub, "; + { + auto modes = channel.accessRoomModes(); + + if (modes->r9k) text += "r9k, "; + if (modes->slowMode) + text += + QString("slow(%1), ").arg(QString::number(modes->slowMode)); + if (modes->emoteOnly) text += "emote, "; + if (modes->submode) text += "sub, "; + } + + if (text.length() > 2) { + text = text.mid(0, text.size() - 2); + } + + if (!text.isEmpty()) { + static QRegularExpression commaReplacement("^(.+?, .+?,) (.+)$"); + + auto match = commaReplacement.match(text); + if (match.hasMatch()) + text = match.captured(1) + '\n' + match.captured(2); + } + + if (text.isEmpty() && channel.hasModRights()) return "none"; + + return text; } - - if (text.length() > 2) { - text = text.mid(0, text.size() - 2); + auto formatTooltip(const TwitchChannel::StreamStatus &s) + { + return QStringList{"", + "

", + s.title, + "

", + s.game, + "
", + s.rerun ? "Vod-casting" : "Live", + " for ", + s.uptime, + " with ", + QString::number(s.viewerCount), + " viewers", + "

"} + .join(""); } + auto formatTitle(const TwitchChannel::StreamStatus &s, Settings &settings) + { + auto title = QString(); - if (!text.isEmpty()) { - static QRegularExpression commaReplacement("^(.+?, .+?,) (.+)$"); + // live + if (s.rerun) + title += " (rerun)"; + else if (s.streamType.isEmpty()) + title += " (" + s.streamType + ")"; + else + title += " (live)"; - auto match = commaReplacement.match(text); - if (match.hasMatch()) - text = match.captured(1) + '\n' + match.captured(2); + // description + if (settings.showViewerCount) + title += " - " + QString::number(s.viewerCount); + if (settings.showTitle) title += " - " + s.title; + if (settings.showGame) title += " - " + s.game; + if (settings.showUptime) title += " - " + s.uptime; + + return title; } + auto distance(QPoint a, QPoint b) + { + auto x = std::abs(a.x() - b.x()); + auto y = std::abs(a.y() - b.y()); - if (text.isEmpty() && channel.hasModRights()) return "none"; - - return text; -} -auto formatTooltip(const TwitchChannel::StreamStatus &s) -{ - return QStringList{"", - "

", - s.title, - "

", - s.game, - "
", - s.rerun ? "Vod-casting" : "Live", - " for ", - s.uptime, - " with ", - QString::number(s.viewerCount), - " viewers", - "

"} - .join(""); -} -auto formatTitle(const TwitchChannel::StreamStatus &s, Settings &settings) -{ - auto title = QString(); - - // live - if (s.rerun) - title += " (rerun)"; - else if (s.streamType.isEmpty()) - title += " (" + s.streamType + ")"; - else - title += " (live)"; - - // description - if (settings.showViewerCount) - title += " - " + QString::number(s.viewerCount) + " viewers"; - if (settings.showTitle) title += " - " + s.title; - if (settings.showGame) title += " - " + s.game; - if (settings.showUptime) title += " - uptime: " + s.uptime; - - return title; -} -auto distance(QPoint a, QPoint b) -{ - auto x = std::abs(a.x() - b.x()); - auto y = std::abs(a.y() - b.y()); - - return std::sqrt(x * x + y * y); -} + return std::sqrt(x * x + y * y); + } } // namespace SplitHeader::SplitHeader(Split *_split) diff --git a/src/widgets/splits/SplitInput.cpp b/src/widgets/splits/SplitInput.cpp index 65bc37e5c..f6a66e7f4 100644 --- a/src/widgets/splits/SplitInput.cpp +++ b/src/widgets/splits/SplitInput.cpp @@ -2,14 +2,20 @@ #include "Application.hpp" #include "controllers/commands/CommandController.hpp" +#include "messages/Link.hpp" #include "providers/twitch/TwitchChannel.hpp" #include "providers/twitch/TwitchServer.hpp" #include "singletons/Settings.hpp" #include "singletons/Theme.hpp" #include "util/LayoutCreator.hpp" #include "widgets/Notebook.hpp" +#include "widgets/dialogs/EmotePopup.hpp" +#include "widgets/helper/ChannelView.hpp" +#include "widgets/helper/EffectLabel.hpp" +#include "widgets/helper/ResizingTextEdit.hpp" #include "widgets/splits/Split.hpp" #include "widgets/splits/SplitContainer.hpp" +#include "widgets/splits/SplitInput.hpp" #include #include @@ -70,11 +76,11 @@ void SplitInput::initLayout() // set edit font this->ui_.textEdit->setFont( - app->fonts->getFont(Fonts::Type::ChatMedium, this->getScale())); + app->fonts->getFont(FontStyle::ChatMedium, this->getScale())); this->managedConnections_.push_back(app->fonts->fontChanged.connect([=]() { this->ui_.textEdit->setFont( - app->fonts->getFont(Fonts::Type::ChatMedium, this->getScale())); + app->fonts->getFont(FontStyle::ChatMedium, this->getScale())); })); // open emote popup @@ -98,12 +104,12 @@ void SplitInput::initLayout() QObject::connect(this->ui_.textEdit, &QTextEdit::copyAvailable, [this](bool available) { if (available) { - this->split_->view_.clearSelection(); + this->split_->view_->clearSelection(); } }); // textEditLength visibility - app->settings->showMessageLength.connect( + getSettings()->showMessageLength.connect( [this](const bool &value, auto) { this->ui_.textEditLength->setHidden(!value); }, @@ -273,7 +279,7 @@ void SplitInput::installKeyPressedEvent() } } else if (event->key() == Qt::Key_C && event->modifiers() == Qt::ControlModifier) { - if (this->split_->view_.hasSelection()) { + if (this->split_->view_->hasSelection()) { this->split_->copyToClipboard(); event->accept(); } diff --git a/src/widgets/splits/SplitInput.hpp b/src/widgets/splits/SplitInput.hpp index ddcf2c97d..6ad6f4f83 100644 --- a/src/widgets/splits/SplitInput.hpp +++ b/src/widgets/splits/SplitInput.hpp @@ -1,9 +1,6 @@ #pragma once #include "widgets/BaseWidget.hpp" -#include "widgets/dialogs/EmotePopup.hpp" -#include "widgets/helper/ResizingTextEdit.hpp" -#include "widgets/helper/EffectLabel.hpp" #include #include @@ -16,6 +13,9 @@ namespace chatterino { class Split; +class EmotePopup; +class EffectLabel; +class ResizingTextEdit; class SplitInput : public BaseWidget { @@ -45,7 +45,7 @@ private: void updateEmoteButton(); Split *const split_; - std::unique_ptr emotePopup_; + std::shared_ptr emotePopup_; struct { ResizingTextEdit *textEdit; diff --git a/src/widgets/splits/SplitOverlay.cpp b/src/widgets/splits/SplitOverlay.cpp index 268811b20..1b38c0ca2 100644 --- a/src/widgets/splits/SplitOverlay.cpp +++ b/src/widgets/splits/SplitOverlay.cpp @@ -10,6 +10,7 @@ #include "Application.hpp" #include "singletons/Resources.hpp" +#include "singletons/Theme.hpp" #include "widgets/splits/Split.hpp" #include "widgets/splits/SplitContainer.hpp"