From 9fa9d7f0e3e346ad381d9bd0c27b63943976722d Mon Sep 17 00:00:00 2001 From: Rasmus Karlsson Date: Sun, 7 Jan 2018 02:56:45 +0100 Subject: [PATCH] Implement preferred emote quality setting. This doesn't work super well for Twitch emotes because they don't conform to a proper emote scaling standard Fixes #150 --- src/singletons/emotemanager.cpp | 128 +++++++++++++++++++--------- src/singletons/emotemanager.hpp | 5 +- src/singletons/settingsmanager.hpp | 7 ++ src/twitch/twitchmessagebuilder.cpp | 36 ++++---- src/twitch/twitchmessagebuilder.hpp | 2 +- src/util/emotemap.hpp | 48 ++++++++++- src/widgets/emotepopup.cpp | 17 ++-- src/widgets/settingsdialog.cpp | 23 +++++ 8 files changed, 194 insertions(+), 72 deletions(-) diff --git a/src/singletons/emotemanager.cpp b/src/singletons/emotemanager.cpp index 820f68f7a..ddbbcf6ea 100644 --- a/src/singletons/emotemanager.cpp +++ b/src/singletons/emotemanager.cpp @@ -16,13 +16,65 @@ #include -#define TWITCH_EMOTE_TEMPLATE "https://static-cdn.jtvnw.net/emoticons/v1/{id}/{scale}.0" +#define TWITCH_EMOTE_TEMPLATE "https://static-cdn.jtvnw.net/emoticons/v1/{id}/{scale}" using namespace chatterino::messages; namespace chatterino { namespace singletons { +namespace { + +static QString GetTwitchEmoteLink(long id, const QString &emoteScale) +{ + QString value = TWITCH_EMOTE_TEMPLATE; + + value.detach(); + + return value.replace("{id}", QString::number(id)).replace("{scale}", emoteScale); +} + +static QString GetBTTVEmoteLink(QString urlTemplate, const QString &id, const QString &emoteScale) +{ + urlTemplate.detach(); + + return urlTemplate.replace("{{id}}", id).replace("{{image}}", emoteScale); +} + +static QString GetFFZEmoteLink(const QJsonObject &urls, const QString &emoteScale) +{ + auto emote = urls.value(emoteScale); + if (emote.isUndefined()) { + return ""; + } + + assert(emote.isString()); + + return "http:" + emote.toString(); +} + +static void FillInFFZEmoteData(const QJsonObject &urls, const QString &code, + util::EmoteData &emoteData) +{ + QString url1x = GetFFZEmoteLink(urls, "1"); + QString url2x = GetFFZEmoteLink(urls, "2"); + QString url3x = GetFFZEmoteLink(urls, "4"); + + assert(!url1x.isEmpty()); + + emoteData.image1x = new LazyLoadedImage(url1x, 1, code, code + "
Global FFZ Emote"); + + if (!url2x.isEmpty()) { + emoteData.image2x = new LazyLoadedImage(url2x, 0.5, code, code + "
Global FFZ Emote"); + } + + if (!url3x.isEmpty()) { + emoteData.image3x = new LazyLoadedImage(url3x, 0.25, code, code + "
Global FFZ Emote"); + } +} + +} // namespace + EmoteManager::EmoteManager(SettingManager &_settingsManager, WindowManager &_windowManager) : settingsManager(_settingsManager) , windowManager(_windowManager) @@ -136,12 +188,13 @@ void EmoteManager::reloadFFZChannelEmotes(const QString &channelName, QString code = emoteObject.value("name").toString(); QJsonObject urls = emoteObject.value("urls").toObject(); - QString url1 = "http:" + urls.value("1").toString(); auto emote = - this->getFFZChannelEmoteFromCaches().getOrAdd(id, [this, &code, &url1] { - return util::EmoteData( - new LazyLoadedImage(url1, 1, code, code + "
Channel FFZ Emote")); + this->getFFZChannelEmoteFromCaches().getOrAdd(id, [this, &code, &urls] { + util::EmoteData emoteData; + FillInFFZEmoteData(urls, code, emoteData); + + return emoteData; }); this->ffzChannelEmotes.insert(code, emote); @@ -310,8 +363,9 @@ void EmoteManager::parseEmojis(std::vector> if (charactersFromLastParsedEmoji > 0) { // Add characters inbetween emojis - parsedWords.push_back(std::tuple( - nullptr, text.mid(lastParsedEmojiEndIndex, charactersFromLastParsedEmoji))); + parsedWords.push_back(std::tuple( + util::EmoteData(), + text.mid(lastParsedEmojiEndIndex, charactersFromLastParsedEmoji))); } QString url = "https://cdnjs.cloudflare.com/ajax/libs/" @@ -334,8 +388,8 @@ void EmoteManager::parseEmojis(std::vector> if (lastParsedEmojiEndIndex < text.length()) { // Add remaining characters - parsedWords.push_back(std::tuple( - nullptr, text.mid(lastParsedEmojiEndIndex))); + parsedWords.push_back(std::tuple( + util::EmoteData(), text.mid(lastParsedEmojiEndIndex))); } } @@ -413,7 +467,7 @@ void EmoteManager::refreshTwitchEmotes(const std::shared_ptr emoteData.filled = true; } - ); + ); } void EmoteManager::loadBTTVEmotes() @@ -427,20 +481,22 @@ void EmoteManager::loadBTTVEmotes() debug::Log("Got global bttv emotes"); auto emotes = root.value("emotes").toArray(); - QString linkTemplate = "https:" + root.value("urlTemplate").toString(); + QString urlTemplate = "https:" + root.value("urlTemplate").toString(); std::vector codes; for (const QJsonValue &emote : emotes) { QString id = emote.toObject().value("id").toString(); QString code = emote.toObject().value("code").toString(); - // emote.value("imageType").toString(); - QString tmp = linkTemplate; - tmp.detach(); - QString url = tmp.replace("{{id}}", id).replace("{{image}}", "1x"); + util::EmoteData emoteData; + emoteData.image1x = new LazyLoadedImage(GetBTTVEmoteLink(urlTemplate, id, "1x"), 1, + code, code + "
Global BTTV Emote"); + emoteData.image2x = new LazyLoadedImage(GetBTTVEmoteLink(urlTemplate, id, "2x"), 0.5, + code, code + "
Global BTTV Emote"); + emoteData.image3x = new LazyLoadedImage(GetBTTVEmoteLink(urlTemplate, id, "3x"), 0.25, + code, code + "
Global BTTV Emote"); - this->bttvGlobalEmotes.insert( - code, new LazyLoadedImage(url, 1, code, code + "
Global BTTV Emote")); + this->bttvGlobalEmotes.insert(code, emoteData); codes.push_back(code.toStdString()); } @@ -467,46 +523,38 @@ void EmoteManager::loadFFZEmotes() for (const QJsonValue &emote : emoticons) { QJsonObject object = emote.toObject(); - // margins - - // int id = object.value("id").toInt(); QString code = object.value("name").toString(); - QJsonObject urls = object.value("urls").toObject(); - QString url1 = "http:" + urls.value("1").toString(); - this->ffzGlobalEmotes.insert( - code, new LazyLoadedImage(url1, 1, code, code + "
Global FFZ Emote")); + util::EmoteData emoteData; + FillInFFZEmoteData(urls, code, emoteData); + + this->ffzGlobalEmotes.insert(code, emoteData); codes.push_back(code.toStdString()); } this->ffzGlobalEmoteCodes = codes; } }); -} // namespace chatterino +} // id is used for lookup // emoteName is used for giving a name to the emote in case it doesn't exist util::EmoteData EmoteManager::getTwitchEmoteById(long id, const QString &emoteName) { return _twitchEmoteFromCache.getOrAdd(id, [this, &emoteName, &id] { - qreal scale; - QString url = getTwitchEmoteLink(id, scale); - return new LazyLoadedImage(url, scale, emoteName, emoteName + "
Twitch Emote"); + util::EmoteData newEmoteData; + newEmoteData.image1x = new LazyLoadedImage(GetTwitchEmoteLink(id, "1.0"), 1, emoteName, + emoteName + "
Twitch Emote 1x"); + newEmoteData.image2x = new LazyLoadedImage(GetTwitchEmoteLink(id, "2.0"), .5, emoteName, + emoteName + "
Twitch Emote 2x"); + newEmoteData.image3x = new LazyLoadedImage(GetTwitchEmoteLink(id, "3.0"), .25, emoteName, + emoteName + "
Twitch Emote 3x"); + + return newEmoteData; }); } -QString EmoteManager::getTwitchEmoteLink(long id, qreal &scale) -{ - scale = .5; - - QString value = TWITCH_EMOTE_TEMPLATE; - - value.detach(); - - return value.replace("{id}", QString::number(id)).replace("{scale}", "2"); -} - util::EmoteData EmoteManager::getCheerImage(long long amount, bool animated) { // TODO: fix this xD @@ -539,5 +587,5 @@ boost::signals2::signal &EmoteManager::getGifUpdateSignal() return this->gifUpdateTimerSignal; } +} // namespace singletons } // namespace chatterino -} diff --git a/src/singletons/emotemanager.hpp b/src/singletons/emotemanager.hpp index db276d11c..eacf9fc6d 100644 --- a/src/singletons/emotemanager.hpp +++ b/src/singletons/emotemanager.hpp @@ -153,10 +153,7 @@ private: bool gifUpdateTimerInitiated = false; int _generation = 0; - - // methods - static QString getTwitchEmoteLink(long id, qreal &scale); }; +} // namespace singletons } // namespace chatterino -} diff --git a/src/singletons/settingsmanager.hpp b/src/singletons/settingsmanager.hpp index f9b4d6719..801d8cbaa 100644 --- a/src/singletons/settingsmanager.hpp +++ b/src/singletons/settingsmanager.hpp @@ -18,6 +18,7 @@ class SettingManager : public QObject using BoolSetting = ChatterinoSetting; using FloatSetting = ChatterinoSetting; + using IntSetting = ChatterinoSetting; using StringSetting = ChatterinoSetting; using QStringSetting = ChatterinoSetting; @@ -64,6 +65,12 @@ public: BoolSetting enableGifAnimations = {"/emotes/enableGifAnimations", true}; FloatSetting emoteScale = {"/emotes/scale", 1.f}; + // 0 = Smallest size + // 1 = One size above 0 (usually size of 0 * 2) + // 2 = One size above 1 (usually size of 1 * 2) + // etc... + IntSetting preferredEmoteQuality = {"/emotes/preferredEmoteQuality", 0}; + /// Links BoolSetting linksDoubleClickOnly = {"/links/doubleClickToOpen", false}; diff --git a/src/twitch/twitchmessagebuilder.cpp b/src/twitch/twitchmessagebuilder.cpp index 1a754bbf4..8e220c3c0 100644 --- a/src/twitch/twitchmessagebuilder.cpp +++ b/src/twitch/twitchmessagebuilder.cpp @@ -34,6 +34,8 @@ SharedMessage TwitchMessageBuilder::parse() singletons::SettingManager &settings = singletons::SettingManager::getInstance(); singletons::EmoteManager &emoteManager = singletons::EmoteManager::getInstance(); + auto preferredEmoteQuality = settings.preferredEmoteQuality.getValue(); + this->originalMessage = this->ircMessage->content(); this->parseUsername(); @@ -121,14 +123,9 @@ SharedMessage TwitchMessageBuilder::parse() // twitch emote if (currentTwitchEmote != twitchEmotes.end() && currentTwitchEmote->first == i) { - this->appendWord( - Word(currentTwitchEmote->second.image, Word::TwitchEmoteImage, - currentTwitchEmote->second.image->getName(), - currentTwitchEmote->second.image->getName() + QString("\nTwitch Emote"))); - this->appendWord( - Word(currentTwitchEmote->second.image->getName(), Word::TwitchEmoteText, textColor, - singletons::FontManager::Medium, currentTwitchEmote->second.image->getName(), - currentTwitchEmote->second.image->getName() + QString("\nTwitch Emote"))); + auto emoteImage = currentTwitchEmote->second.getImageForSize(preferredEmoteQuality); + this->appendWord(Word(emoteImage, Word::TwitchEmoteImage, emoteImage->getName(), + emoteImage->getTooltip(), Link(Link::Url, emoteImage->getUrl()))); i += split.length() + 1; currentTwitchEmote = std::next(currentTwitchEmote); @@ -145,7 +142,7 @@ SharedMessage TwitchMessageBuilder::parse() for (const auto &tuple : parsed) { const util::EmoteData &emoteData = std::get<0>(tuple); - if (emoteData.image == nullptr) { // is text + if (!emoteData.isValid()) { // is text QString string = std::get<1>(tuple); static QRegularExpression cheerRegex("cheer[1-9][0-9]*"); @@ -234,11 +231,12 @@ SharedMessage TwitchMessageBuilder::parse() this->appendWord(Word(string, Word::Text, textColor, singletons::FontManager::Medium, string, QString(), link)); } else { // is emoji - this->appendWord(Word(emoteData.image, Word::EmojiImage, emoteData.image->getName(), - emoteData.image->getTooltip())); - Word(emoteData.image->getName(), Word::EmojiText, textColor, - singletons::FontManager::Medium, emoteData.image->getName(), - emoteData.image->getTooltip()); + auto emoteImage = emoteData.getImageForSize(preferredEmoteQuality); + this->appendWord(Word(emoteImage, Word::EmojiImage, emoteImage->getName(), + emoteImage->getTooltip())); + Word(emoteImage->getName(), Word::EmojiText, textColor, + singletons::FontManager::Medium, emoteImage->getName(), + emoteImage->getTooltip()); } } @@ -547,11 +545,13 @@ bool TwitchMessageBuilder::tryAppendEmote(QString &emoteString) return false; } -bool TwitchMessageBuilder::appendEmote(util::EmoteData &emoteData) +bool TwitchMessageBuilder::appendEmote(const util::EmoteData &emoteData) { - this->appendWord(Word(emoteData.image, Word::BttvEmoteImage, emoteData.image->getName(), - emoteData.image->getTooltip(), - Link(Link::Url, emoteData.image->getUrl()))); + auto emoteImage = emoteData.getImageForSize( + singletons::SettingManager::getInstance().preferredEmoteQuality.getValue()); + + this->appendWord(Word(emoteImage, Word::BttvEmoteImage, emoteImage->getName(), + emoteImage->getTooltip(), Link(Link::Url, emoteImage->getUrl()))); // Perhaps check for ignored emotes here? return true; diff --git a/src/twitch/twitchmessagebuilder.hpp b/src/twitch/twitchmessagebuilder.hpp index 9a281217e..cd947874f 100644 --- a/src/twitch/twitchmessagebuilder.hpp +++ b/src/twitch/twitchmessagebuilder.hpp @@ -60,7 +60,7 @@ private: void appendTwitchEmote(const Communi::IrcPrivateMessage *ircMessage, const QString &emote, std::vector> &vec); bool tryAppendEmote(QString &emoteString); - bool appendEmote(util::EmoteData &emoteData); + bool appendEmote(const util::EmoteData &emoteData); void parseTwitchBadges(); void parseChatterinoBadges(); diff --git a/src/util/emotemap.hpp b/src/util/emotemap.hpp index f285169e5..fba5b4b6e 100644 --- a/src/util/emotemap.hpp +++ b/src/util/emotemap.hpp @@ -3,6 +3,8 @@ #include "messages/lazyloadedimage.hpp" #include "util/concurrentmap.hpp" +#include + namespace chatterino { namespace util { @@ -12,13 +14,51 @@ struct EmoteData { } EmoteData(messages::LazyLoadedImage *_image) - : image(_image) + : image1x(_image) { } - messages::LazyLoadedImage *image = nullptr; + messages::LazyLoadedImage *getImageForSize(unsigned emoteSize) const + { + messages::LazyLoadedImage *ret = nullptr; + + switch (emoteSize) { + case 0: + ret = this->image1x; + break; + case 1: + ret = this->image2x; + break; + case 2: + ret = this->image3x; + break; + + default: + ret = this->image1x; + break; + } + + if (ret == nullptr) { + ret = this->image1x; + } + + assert(ret != nullptr); + + return ret; + } + + // Emotes must have a 1x image to be valid + bool isValid() const + { + return this->image1x != nullptr; + } + + messages::LazyLoadedImage *image1x = nullptr; + messages::LazyLoadedImage *image2x = nullptr; + messages::LazyLoadedImage *image3x = nullptr; }; typedef ConcurrentMap EmoteMap; -} -} + +} // namespace util +} // namespace chatterino diff --git a/src/widgets/emotepopup.cpp b/src/widgets/emotepopup.cpp index bd3165fed..95d202abe 100644 --- a/src/widgets/emotepopup.cpp +++ b/src/widgets/emotepopup.cpp @@ -59,8 +59,11 @@ void EmotePopup::loadChannel(std::shared_ptr _channel) builder2.getMessage()->centered = true; builder2.getMessage()->setDisableCompactEmotes(true); + int preferredEmoteSize = singletons::SettingManager::getInstance().preferredEmoteQuality; + map.each([&](const QString &key, const util::EmoteData &value) { - builder2.appendWord(Word(value.image, Word::Flags::AlwaysShow, key, emoteDesc, + builder2.appendWord(Word(value.getImageForSize(preferredEmoteSize), + Word::Flags::AlwaysShow, key, emoteDesc, Link(Link::Type::InsertText, key))); }); @@ -82,6 +85,7 @@ void EmotePopup::loadChannel(std::shared_ptr _channel) void EmotePopup::loadEmojis() { + int preferredEmoteSize = singletons::SettingManager::getInstance().preferredEmoteQuality; util::EmoteMap &emojis = singletons::EmoteManager::getInstance().getEmojis(); std::shared_ptr emojiChannel(new Channel("")); @@ -99,10 +103,13 @@ void EmotePopup::loadEmojis() messages::MessageBuilder builder; builder.getMessage()->centered = true; builder.getMessage()->setDisableCompactEmotes(true); - emojis.each([this, &builder](const QString &key, const util::EmoteData &value) { - builder.appendWord(Word(value.image, Word::Flags::AlwaysShow, key, "emoji", - Link(Link::Type::InsertText, key))); - }); + + emojis.each( + [this, &builder, preferredEmoteSize](const QString &key, const util::EmoteData &value) { + auto emoteImage = value.getImageForSize(preferredEmoteSize); + builder.appendWord(Word(emoteImage, Word::Flags::AlwaysShow, key, "emoji", + Link(Link::Type::InsertText, key))); + }); emojiChannel->addMessage(builder.getMessage()); this->viewEmojis->setChannel(emojiChannel); diff --git a/src/widgets/settingsdialog.cpp b/src/widgets/settingsdialog.cpp index bfa2a541d..edcfc7948 100644 --- a/src/widgets/settingsdialog.cpp +++ b/src/widgets/settingsdialog.cpp @@ -426,6 +426,29 @@ QVBoxLayout *SettingsDialog::createEmotesTab() layout->addWidget(createCheckbox("Enable Twitch Emotes", settings.enableTwitchEmotes)); + // Preferred emote quality + { + auto box = new QHBoxLayout(); + auto label = new QLabel("Preferred emote quality"); + label->setToolTip("Select which emote quality you prefer to download"); + auto widget = new QComboBox(); + widget->addItems({"1x", "2x", "4x"}); + box->addWidget(label); + box->addWidget(widget); + + settings.preferredEmoteQuality.connect([widget](int newIndex, auto) { + widget->setCurrentIndex(newIndex); // + }); + + QObject::connect( + widget, QOverload::of(&QComboBox::currentIndexChanged), this, [](int index) { + singletons::SettingManager &settings = singletons::SettingManager::getInstance(); + settings.preferredEmoteQuality = index; // + }); + + layout->addLayout(box); + } + return layout; }