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>
#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 + "<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)
: 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 + "<br/>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<std::tuple<util::EmoteData, QString>>
if (charactersFromLastParsedEmoji > 0) {
// Add characters inbetween emojis
parsedWords.push_back(std::tuple<messages::LazyLoadedImage *, QString>(
nullptr, text.mid(lastParsedEmojiEndIndex, charactersFromLastParsedEmoji)));
parsedWords.push_back(std::tuple<util::EmoteData, QString>(
util::EmoteData(),
text.mid(lastParsedEmojiEndIndex, charactersFromLastParsedEmoji)));
}
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()) {
// Add remaining characters
parsedWords.push_back(std::tuple<messages::LazyLoadedImage *, QString>(
nullptr, text.mid(lastParsedEmojiEndIndex)));
parsedWords.push_back(std::tuple<util::EmoteData, QString>(
util::EmoteData(), text.mid(lastParsedEmojiEndIndex)));
}
}
@ -413,7 +467,7 @@ void EmoteManager::refreshTwitchEmotes(const std::shared_ptr<twitch::TwitchUser>
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<std::string> 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 + "<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(
code, new LazyLoadedImage(url, 1, code, code + "<br/>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 + "<br/>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 + "<br/>Twitch Emote");
util::EmoteData newEmoteData;
newEmoteData.image1x = new LazyLoadedImage(GetTwitchEmoteLink(id, "1.0"), 1, emoteName,
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)
{
// TODO: fix this xD
@ -539,5 +587,5 @@ boost::signals2::signal<void()> &EmoteManager::getGifUpdateSignal()
return this->gifUpdateTimerSignal;
}
} // namespace singletons
} // namespace chatterino
}

View file

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

View file

@ -18,6 +18,7 @@ class SettingManager : public QObject
using BoolSetting = ChatterinoSetting<bool>;
using FloatSetting = ChatterinoSetting<float>;
using IntSetting = ChatterinoSetting<int>;
using StringSetting = ChatterinoSetting<std::string>;
using QStringSetting = ChatterinoSetting<QString>;
@ -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};

View file

@ -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;

View file

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

View file

@ -3,6 +3,8 @@
#include "messages/lazyloadedimage.hpp"
#include "util/concurrentmap.hpp"
#include <cassert>
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<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()->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> _channel)
void EmotePopup::loadEmojis()
{
int preferredEmoteSize = singletons::SettingManager::getInstance().preferredEmoteQuality;
util::EmoteMap &emojis = singletons::EmoteManager::getInstance().getEmojis();
std::shared_ptr<Channel> 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);

View file

@ -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<int>::of(&QComboBox::currentIndexChanged), this, [](int index) {
singletons::SettingManager &settings = singletons::SettingManager::getInstance();
settings.preferredEmoteQuality = index; //
});
layout->addLayout(box);
}
return layout;
}