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
This commit is contained in:
Rasmus Karlsson 2018-01-07 02:56:45 +01:00
parent 5baba39cdc
commit 9fa9d7f0e3
8 changed files with 194 additions and 72 deletions

View file

@ -16,13 +16,65 @@
#include <memory> #include <memory>
#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; using namespace chatterino::messages;
namespace chatterino { namespace chatterino {
namespace singletons { 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 + "<br />Global FFZ Emote");
if (!url2x.isEmpty()) {
emoteData.image2x = new LazyLoadedImage(url2x, 0.5, code, code + "<br />Global FFZ Emote");
}
if (!url3x.isEmpty()) {
emoteData.image3x = new LazyLoadedImage(url3x, 0.25, code, code + "<br />Global FFZ Emote");
}
}
} // namespace
EmoteManager::EmoteManager(SettingManager &_settingsManager, WindowManager &_windowManager) EmoteManager::EmoteManager(SettingManager &_settingsManager, WindowManager &_windowManager)
: settingsManager(_settingsManager) : settingsManager(_settingsManager)
, windowManager(_windowManager) , windowManager(_windowManager)
@ -136,12 +188,13 @@ void EmoteManager::reloadFFZChannelEmotes(const QString &channelName,
QString code = emoteObject.value("name").toString(); QString code = emoteObject.value("name").toString();
QJsonObject urls = emoteObject.value("urls").toObject(); QJsonObject urls = emoteObject.value("urls").toObject();
QString url1 = "http:" + urls.value("1").toString();
auto emote = auto emote =
this->getFFZChannelEmoteFromCaches().getOrAdd(id, [this, &code, &url1] { this->getFFZChannelEmoteFromCaches().getOrAdd(id, [this, &code, &urls] {
return util::EmoteData( util::EmoteData emoteData;
new LazyLoadedImage(url1, 1, code, code + "<br/>Channel FFZ Emote")); FillInFFZEmoteData(urls, code, emoteData);
return emoteData;
}); });
this->ffzChannelEmotes.insert(code, emote); this->ffzChannelEmotes.insert(code, emote);
@ -310,8 +363,9 @@ void EmoteManager::parseEmojis(std::vector<std::tuple<util::EmoteData, QString>>
if (charactersFromLastParsedEmoji > 0) { if (charactersFromLastParsedEmoji > 0) {
// Add characters inbetween emojis // Add characters inbetween emojis
parsedWords.push_back(std::tuple<messages::LazyLoadedImage *, QString>( parsedWords.push_back(std::tuple<util::EmoteData, QString>(
nullptr, text.mid(lastParsedEmojiEndIndex, charactersFromLastParsedEmoji))); util::EmoteData(),
text.mid(lastParsedEmojiEndIndex, charactersFromLastParsedEmoji)));
} }
QString url = "https://cdnjs.cloudflare.com/ajax/libs/" QString url = "https://cdnjs.cloudflare.com/ajax/libs/"
@ -334,8 +388,8 @@ void EmoteManager::parseEmojis(std::vector<std::tuple<util::EmoteData, QString>>
if (lastParsedEmojiEndIndex < text.length()) { if (lastParsedEmojiEndIndex < text.length()) {
// Add remaining characters // Add remaining characters
parsedWords.push_back(std::tuple<messages::LazyLoadedImage *, QString>( parsedWords.push_back(std::tuple<util::EmoteData, QString>(
nullptr, text.mid(lastParsedEmojiEndIndex))); util::EmoteData(), text.mid(lastParsedEmojiEndIndex)));
} }
} }
@ -427,20 +481,22 @@ void EmoteManager::loadBTTVEmotes()
debug::Log("Got global bttv emotes"); debug::Log("Got global bttv emotes");
auto emotes = root.value("emotes").toArray(); auto emotes = root.value("emotes").toArray();
QString linkTemplate = "https:" + root.value("urlTemplate").toString(); QString urlTemplate = "https:" + root.value("urlTemplate").toString();
std::vector<std::string> codes; std::vector<std::string> codes;
for (const QJsonValue &emote : emotes) { for (const QJsonValue &emote : emotes) {
QString id = emote.toObject().value("id").toString(); QString id = emote.toObject().value("id").toString();
QString code = emote.toObject().value("code").toString(); QString code = emote.toObject().value("code").toString();
// emote.value("imageType").toString();
QString tmp = linkTemplate; util::EmoteData emoteData;
tmp.detach(); emoteData.image1x = new LazyLoadedImage(GetBTTVEmoteLink(urlTemplate, id, "1x"), 1,
QString url = tmp.replace("{{id}}", id).replace("{{image}}", "1x"); code, code + "<br />Global BTTV Emote");
emoteData.image2x = new LazyLoadedImage(GetBTTVEmoteLink(urlTemplate, id, "2x"), 0.5,
code, code + "<br />Global BTTV Emote");
emoteData.image3x = new LazyLoadedImage(GetBTTVEmoteLink(urlTemplate, id, "3x"), 0.25,
code, code + "<br />Global BTTV Emote");
this->bttvGlobalEmotes.insert( this->bttvGlobalEmotes.insert(code, emoteData);
code, new LazyLoadedImage(url, 1, code, code + "<br/>Global BTTV Emote"));
codes.push_back(code.toStdString()); codes.push_back(code.toStdString());
} }
@ -467,46 +523,38 @@ void EmoteManager::loadFFZEmotes()
for (const QJsonValue &emote : emoticons) { for (const QJsonValue &emote : emoticons) {
QJsonObject object = emote.toObject(); QJsonObject object = emote.toObject();
// margins
// int id = object.value("id").toInt();
QString code = object.value("name").toString(); QString code = object.value("name").toString();
QJsonObject urls = object.value("urls").toObject(); QJsonObject urls = object.value("urls").toObject();
QString url1 = "http:" + urls.value("1").toString();
this->ffzGlobalEmotes.insert( util::EmoteData emoteData;
code, new LazyLoadedImage(url1, 1, code, code + "<br/>Global FFZ Emote")); FillInFFZEmoteData(urls, code, emoteData);
this->ffzGlobalEmotes.insert(code, emoteData);
codes.push_back(code.toStdString()); codes.push_back(code.toStdString());
} }
this->ffzGlobalEmoteCodes = codes; this->ffzGlobalEmoteCodes = codes;
} }
}); });
} // namespace chatterino }
// id is used for lookup // id is used for lookup
// emoteName is used for giving a name to the emote in case it doesn't exist // 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) util::EmoteData EmoteManager::getTwitchEmoteById(long id, const QString &emoteName)
{ {
return _twitchEmoteFromCache.getOrAdd(id, [this, &emoteName, &id] { return _twitchEmoteFromCache.getOrAdd(id, [this, &emoteName, &id] {
qreal scale; util::EmoteData newEmoteData;
QString url = getTwitchEmoteLink(id, scale); newEmoteData.image1x = new LazyLoadedImage(GetTwitchEmoteLink(id, "1.0"), 1, emoteName,
return new LazyLoadedImage(url, scale, emoteName, emoteName + "<br/>Twitch Emote"); emoteName + "<br/>Twitch Emote 1x");
newEmoteData.image2x = new LazyLoadedImage(GetTwitchEmoteLink(id, "2.0"), .5, emoteName,
emoteName + "<br/>Twitch Emote 2x");
newEmoteData.image3x = new LazyLoadedImage(GetTwitchEmoteLink(id, "3.0"), .25, emoteName,
emoteName + "<br/>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) util::EmoteData EmoteManager::getCheerImage(long long amount, bool animated)
{ {
// TODO: fix this xD // TODO: fix this xD
@ -539,5 +587,5 @@ boost::signals2::signal<void()> &EmoteManager::getGifUpdateSignal()
return this->gifUpdateTimerSignal; return this->gifUpdateTimerSignal;
} }
} // namespace singletons
} // namespace chatterino } // namespace chatterino
}

View file

@ -153,10 +153,7 @@ private:
bool gifUpdateTimerInitiated = false; bool gifUpdateTimerInitiated = false;
int _generation = 0; int _generation = 0;
// methods
static QString getTwitchEmoteLink(long id, qreal &scale);
}; };
} // namespace singletons
} // namespace chatterino } // namespace chatterino
}

View file

@ -18,6 +18,7 @@ class SettingManager : public QObject
using BoolSetting = ChatterinoSetting<bool>; using BoolSetting = ChatterinoSetting<bool>;
using FloatSetting = ChatterinoSetting<float>; using FloatSetting = ChatterinoSetting<float>;
using IntSetting = ChatterinoSetting<int>;
using StringSetting = ChatterinoSetting<std::string>; using StringSetting = ChatterinoSetting<std::string>;
using QStringSetting = ChatterinoSetting<QString>; using QStringSetting = ChatterinoSetting<QString>;
@ -64,6 +65,12 @@ public:
BoolSetting enableGifAnimations = {"/emotes/enableGifAnimations", true}; BoolSetting enableGifAnimations = {"/emotes/enableGifAnimations", true};
FloatSetting emoteScale = {"/emotes/scale", 1.f}; 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 /// Links
BoolSetting linksDoubleClickOnly = {"/links/doubleClickToOpen", false}; BoolSetting linksDoubleClickOnly = {"/links/doubleClickToOpen", false};

View file

@ -34,6 +34,8 @@ SharedMessage TwitchMessageBuilder::parse()
singletons::SettingManager &settings = singletons::SettingManager::getInstance(); singletons::SettingManager &settings = singletons::SettingManager::getInstance();
singletons::EmoteManager &emoteManager = singletons::EmoteManager::getInstance(); singletons::EmoteManager &emoteManager = singletons::EmoteManager::getInstance();
auto preferredEmoteQuality = settings.preferredEmoteQuality.getValue();
this->originalMessage = this->ircMessage->content(); this->originalMessage = this->ircMessage->content();
this->parseUsername(); this->parseUsername();
@ -121,14 +123,9 @@ SharedMessage TwitchMessageBuilder::parse()
// twitch emote // twitch emote
if (currentTwitchEmote != twitchEmotes.end() && currentTwitchEmote->first == i) { if (currentTwitchEmote != twitchEmotes.end() && currentTwitchEmote->first == i) {
this->appendWord( auto emoteImage = currentTwitchEmote->second.getImageForSize(preferredEmoteQuality);
Word(currentTwitchEmote->second.image, Word::TwitchEmoteImage, this->appendWord(Word(emoteImage, Word::TwitchEmoteImage, emoteImage->getName(),
currentTwitchEmote->second.image->getName(), emoteImage->getTooltip(), Link(Link::Url, emoteImage->getUrl())));
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")));
i += split.length() + 1; i += split.length() + 1;
currentTwitchEmote = std::next(currentTwitchEmote); currentTwitchEmote = std::next(currentTwitchEmote);
@ -145,7 +142,7 @@ SharedMessage TwitchMessageBuilder::parse()
for (const auto &tuple : parsed) { for (const auto &tuple : parsed) {
const util::EmoteData &emoteData = std::get<0>(tuple); 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); QString string = std::get<1>(tuple);
static QRegularExpression cheerRegex("cheer[1-9][0-9]*"); static QRegularExpression cheerRegex("cheer[1-9][0-9]*");
@ -234,11 +231,12 @@ SharedMessage TwitchMessageBuilder::parse()
this->appendWord(Word(string, Word::Text, textColor, this->appendWord(Word(string, Word::Text, textColor,
singletons::FontManager::Medium, string, QString(), link)); singletons::FontManager::Medium, string, QString(), link));
} else { // is emoji } else { // is emoji
this->appendWord(Word(emoteData.image, Word::EmojiImage, emoteData.image->getName(), auto emoteImage = emoteData.getImageForSize(preferredEmoteQuality);
emoteData.image->getTooltip())); this->appendWord(Word(emoteImage, Word::EmojiImage, emoteImage->getName(),
Word(emoteData.image->getName(), Word::EmojiText, textColor, emoteImage->getTooltip()));
singletons::FontManager::Medium, emoteData.image->getName(), Word(emoteImage->getName(), Word::EmojiText, textColor,
emoteData.image->getTooltip()); singletons::FontManager::Medium, emoteImage->getName(),
emoteImage->getTooltip());
} }
} }
@ -547,11 +545,13 @@ bool TwitchMessageBuilder::tryAppendEmote(QString &emoteString)
return false; 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(), auto emoteImage = emoteData.getImageForSize(
emoteData.image->getTooltip(), singletons::SettingManager::getInstance().preferredEmoteQuality.getValue());
Link(Link::Url, emoteData.image->getUrl())));
this->appendWord(Word(emoteImage, Word::BttvEmoteImage, emoteImage->getName(),
emoteImage->getTooltip(), Link(Link::Url, emoteImage->getUrl())));
// Perhaps check for ignored emotes here? // Perhaps check for ignored emotes here?
return true; return true;

View file

@ -60,7 +60,7 @@ private:
void appendTwitchEmote(const Communi::IrcPrivateMessage *ircMessage, const QString &emote, void appendTwitchEmote(const Communi::IrcPrivateMessage *ircMessage, const QString &emote,
std::vector<std::pair<long, util::EmoteData>> &vec); std::vector<std::pair<long, util::EmoteData>> &vec);
bool tryAppendEmote(QString &emoteString); bool tryAppendEmote(QString &emoteString);
bool appendEmote(util::EmoteData &emoteData); bool appendEmote(const util::EmoteData &emoteData);
void parseTwitchBadges(); void parseTwitchBadges();
void parseChatterinoBadges(); void parseChatterinoBadges();

View file

@ -3,6 +3,8 @@
#include "messages/lazyloadedimage.hpp" #include "messages/lazyloadedimage.hpp"
#include "util/concurrentmap.hpp" #include "util/concurrentmap.hpp"
#include <cassert>
namespace chatterino { namespace chatterino {
namespace util { namespace util {
@ -12,13 +14,51 @@ struct EmoteData {
} }
EmoteData(messages::LazyLoadedImage *_image) 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<QString, EmoteData> EmoteMap; typedef ConcurrentMap<QString, EmoteData> EmoteMap;
}
} } // namespace util
} // namespace chatterino

View file

@ -59,8 +59,11 @@ void EmotePopup::loadChannel(std::shared_ptr<Channel> _channel)
builder2.getMessage()->centered = true; builder2.getMessage()->centered = true;
builder2.getMessage()->setDisableCompactEmotes(true); builder2.getMessage()->setDisableCompactEmotes(true);
int preferredEmoteSize = singletons::SettingManager::getInstance().preferredEmoteQuality;
map.each([&](const QString &key, const util::EmoteData &value) { 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))); Link(Link::Type::InsertText, key)));
}); });
@ -82,6 +85,7 @@ void EmotePopup::loadChannel(std::shared_ptr<Channel> _channel)
void EmotePopup::loadEmojis() void EmotePopup::loadEmojis()
{ {
int preferredEmoteSize = singletons::SettingManager::getInstance().preferredEmoteQuality;
util::EmoteMap &emojis = singletons::EmoteManager::getInstance().getEmojis(); util::EmoteMap &emojis = singletons::EmoteManager::getInstance().getEmojis();
std::shared_ptr<Channel> emojiChannel(new Channel("")); std::shared_ptr<Channel> emojiChannel(new Channel(""));
@ -99,8 +103,11 @@ void EmotePopup::loadEmojis()
messages::MessageBuilder builder; messages::MessageBuilder builder;
builder.getMessage()->centered = true; builder.getMessage()->centered = true;
builder.getMessage()->setDisableCompactEmotes(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", 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))); Link(Link::Type::InsertText, key)));
}); });
emojiChannel->addMessage(builder.getMessage()); emojiChannel->addMessage(builder.getMessage());

View file

@ -426,6 +426,29 @@ QVBoxLayout *SettingsDialog::createEmotesTab()
layout->addWidget(createCheckbox("Enable Twitch Emotes", settings.enableTwitchEmotes)); 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<int>::of(&QComboBox::currentIndexChanged), this, [](int index) {
singletons::SettingManager &settings = singletons::SettingManager::getInstance();
settings.preferredEmoteQuality = index; //
});
layout->addLayout(box);
}
return layout; return layout;
} }