tdesktop/Telegram/SourceFiles/chat_helpers/stickers_emoji_pack.cpp

475 lines
11 KiB
C++
Raw Normal View History

2019-08-01 15:13:02 +01:00
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "chat_helpers/stickers_emoji_pack.h"
#include "history/history_item.h"
#include "ui/emoji_config.h"
2019-08-02 19:19:14 +01:00
#include "ui/text/text_isolated_emoji.h"
#include "ui/image/image_source.h"
2019-08-01 15:13:02 +01:00
#include "main/main_session.h"
2019-08-02 19:19:14 +01:00
#include "data/data_file_origin.h"
2019-08-01 15:13:02 +01:00
#include "data/data_session.h"
#include "data/data_document.h"
2019-08-02 19:19:14 +01:00
#include "base/concurrent_timer.h"
2019-08-01 15:13:02 +01:00
#include "apiwrap.h"
2019-08-02 19:19:14 +01:00
#include "styles/style_history.h"
2019-08-01 15:13:02 +01:00
namespace Stickers {
2019-08-02 19:19:14 +01:00
namespace details {
class EmojiImageLoader {
public:
EmojiImageLoader(
crl::weak_on_queue<EmojiImageLoader> weak,
int id);
[[nodiscard]] QImage prepare(const IsolatedEmoji &emoji);
void switchTo(int id);
private:
crl::weak_on_queue<EmojiImageLoader> _weak;
std::optional<Ui::Emoji::UniversalImages> _images;
base::ConcurrentTimer _unloadTimer;
};
2019-08-01 15:13:02 +01:00
namespace {
constexpr auto kRefreshTimeout = TimeId(7200);
2019-08-02 19:23:52 +01:00
constexpr auto kUnloadTimeout = 86400 * crl::time(1000);
2019-08-02 19:19:14 +01:00
[[nodiscard]] QSize CalculateSize(const IsolatedEmoji &emoji) {
using namespace rpl::mappers;
const auto single = st::largeEmojiSize;
const auto skip = st::largeEmojiSkip;
const auto outline = st::largeEmojiOutline;
const auto count = ranges::count_if(emoji.items, _1 != nullptr);
const auto items = single * count + skip * (count - 1);
return QSize(
2 * outline + items,
2 * outline + single
) * cIntRetinaFactor();
}
class ImageSource : public Images::Source {
public:
explicit ImageSource(
const IsolatedEmoji &emoji,
not_null<crl::object_on_queue<EmojiImageLoader>*> loader);
void load(Data::FileOrigin origin) override;
void loadEvenCancelled(Data::FileOrigin origin) override;
QImage takeLoaded() override;
void unload() override;
void automaticLoad(
Data::FileOrigin origin,
const HistoryItem *item) override;
void automaticLoadSettingsChanged() override;
bool loading() override;
bool displayLoading() override;
void cancel() override;
float64 progress() override;
int loadOffset() override;
const StorageImageLocation &location() override;
void refreshFileReference(const QByteArray &data) override;
std::optional<Storage::Cache::Key> cacheKey() override;
void setDelayedStorageLocation(
const StorageImageLocation &location) override;
void performDelayedLoad(Data::FileOrigin origin) override;
bool isDelayedStorageImage() const override;
void setImageBytes(const QByteArray &bytes) override;
int width() override;
int height() override;
int bytesSize() override;
void setInformation(int size, int width, int height) override;
QByteArray bytesForCache() override;
private:
// While HistoryView::Element-s are almost never destroyed
// we make loading of the image lazy.
not_null<crl::object_on_queue<EmojiImageLoader>*> _loader;
IsolatedEmoji _emoji;
QImage _data;
QByteArray _format;
QByteArray _bytes;
QSize _size;
base::binary_guard _loading;
};
ImageSource::ImageSource(
const IsolatedEmoji &emoji,
not_null<crl::object_on_queue<EmojiImageLoader>*> loader)
: _loader(loader)
, _emoji(emoji)
, _size(CalculateSize(emoji)) {
}
void ImageSource::load(Data::FileOrigin origin) {
if (!_data.isNull()) {
return;
}
if (_bytes.isEmpty()) {
_loader->with([
this,
emoji = _emoji,
guard = _loading.make_guard()
](EmojiImageLoader &loader) mutable {
if (!guard) {
return;
}
crl::on_main(std::move(guard), [this, image = loader.prepare(emoji)]{
_data = image;
Auth().downloaderTaskFinished().notify();
});
});
} else {
_data = App::readImage(_bytes, &_format, false);
}
}
void ImageSource::loadEvenCancelled(Data::FileOrigin origin) {
load(origin);
}
QImage ImageSource::takeLoaded() {
load({});
return _data;
}
void ImageSource::unload() {
if (_bytes.isEmpty() && !_data.isNull()) {
if (_format != "JPG") {
_format = "PNG";
}
{
QBuffer buffer(&_bytes);
_data.save(&buffer, _format);
}
Assert(!_bytes.isEmpty());
}
_data = QImage();
}
void ImageSource::automaticLoad(
Data::FileOrigin origin,
const HistoryItem *item) {
}
void ImageSource::automaticLoadSettingsChanged() {
}
bool ImageSource::loading() {
return _data.isNull() && _bytes.isEmpty();
}
bool ImageSource::displayLoading() {
return false;
}
void ImageSource::cancel() {
}
float64 ImageSource::progress() {
return 1.;
}
int ImageSource::loadOffset() {
return 0;
}
const StorageImageLocation &ImageSource::location() {
return StorageImageLocation::Invalid();
}
void ImageSource::refreshFileReference(const QByteArray &data) {
}
std::optional<Storage::Cache::Key> ImageSource::cacheKey() {
return std::nullopt;
}
void ImageSource::setDelayedStorageLocation(
const StorageImageLocation &location) {
}
void ImageSource::performDelayedLoad(Data::FileOrigin origin) {
}
bool ImageSource::isDelayedStorageImage() const {
return false;
}
void ImageSource::setImageBytes(const QByteArray &bytes) {
}
int ImageSource::width() {
return _size.width();
}
int ImageSource::height() {
return _size.height();
}
int ImageSource::bytesSize() {
return _bytes.size();
}
void ImageSource::setInformation(int size, int width, int height) {
if (width && height) {
_size = QSize(width, height);
}
}
QByteArray ImageSource::bytesForCache() {
auto result = QByteArray();
{
QBuffer buffer(&result);
if (!_data.save(&buffer, _format)) {
if (_data.save(&buffer, "PNG")) {
_format = "PNG";
}
}
}
return result;
}
2019-08-01 15:13:02 +01:00
} // namespace
2019-08-02 19:19:14 +01:00
EmojiImageLoader::EmojiImageLoader(
crl::weak_on_queue<EmojiImageLoader> weak,
int id)
: _weak(std::move(weak))
, _images(std::in_place, id)
, _unloadTimer(_weak.runner(), [=] { _images->clear(); }) {
}
QImage EmojiImageLoader::prepare(const IsolatedEmoji &emoji) {
Expects(_images.has_value());
_images->ensureLoaded();
auto result = QImage(
CalculateSize(emoji),
QImage::Format_ARGB32_Premultiplied);
2019-08-05 11:28:46 +01:00
const auto factor = cIntRetinaFactor();
2019-08-02 19:19:14 +01:00
result.fill(Qt::transparent);
{
QPainter p(&result);
auto x = st::largeEmojiOutline;
const auto y = st::largeEmojiOutline;
for (const auto &single : emoji.items) {
if (!single) {
break;
}
_images->draw(
p,
single,
2019-08-05 11:28:46 +01:00
st::largeEmojiSize * factor,
x * factor,
y * factor);
2019-08-02 19:19:14 +01:00
x += st::largeEmojiSize + st::largeEmojiSkip;
}
}
_unloadTimer.callOnce(kUnloadTimeout);
return result;
}
void EmojiImageLoader::switchTo(int id) {
_images.emplace(id);
}
} // namespace details
EmojiPack::EmojiPack(not_null<Main::Session*> session)
: _session(session)
, _imageLoader(Ui::Emoji::CurrentSetId()) {
2019-08-01 15:13:02 +01:00
refresh();
2019-08-01 22:14:19 +01:00
session->data().itemRemoved(
) | rpl::filter([](not_null<const HistoryItem*> item) {
2019-08-02 19:19:14 +01:00
return item->isIsolatedEmoji();
2019-08-01 22:14:19 +01:00
}) | rpl::start_with_next([=](not_null<const HistoryItem*> item) {
remove(item);
}, _lifetime);
2019-08-02 11:52:35 +01:00
session->settings().largeEmojiChanges(
) | rpl::start_with_next([=] {
2019-08-02 19:19:14 +01:00
refreshAll();
}, _lifetime);
Ui::Emoji::Updated(
) | rpl::start_with_next([=] {
const auto id = Ui::Emoji::CurrentSetId();
_images.clear();
_imageLoader.with([=](details::EmojiImageLoader &loader) {
loader.switchTo(id);
});
refreshAll();
2019-08-02 11:52:35 +01:00
}, _lifetime);
2019-08-01 15:13:02 +01:00
}
2019-08-02 19:19:14 +01:00
EmojiPack::~EmojiPack() = default;
bool EmojiPack::add(not_null<HistoryItem*> item) {
2019-08-01 22:14:19 +01:00
auto length = 0;
2019-08-02 19:19:14 +01:00
if (const auto emoji = item->isolatedEmoji()) {
_items[emoji].emplace(item);
return true;
2019-08-01 22:14:19 +01:00
}
return false;
}
2019-08-02 19:19:14 +01:00
void EmojiPack::remove(not_null<const HistoryItem*> item) {
Expects(item->isIsolatedEmoji());
2019-08-01 15:13:02 +01:00
auto length = 0;
2019-08-02 19:19:14 +01:00
const auto emoji = item->isolatedEmoji();
2019-08-01 22:14:19 +01:00
const auto i = _items.find(emoji);
Assert(i != end(_items));
const auto j = i->second.find(item);
Assert(j != end(i->second));
i->second.erase(j);
if (i->second.empty()) {
_items.erase(i);
}
}
2019-08-02 19:19:14 +01:00
DocumentData *EmojiPack::stickerForEmoji(const IsolatedEmoji &emoji) {
Expects(!emoji.empty());
if (emoji.items[1] != nullptr) {
2019-08-01 15:13:02 +01:00
return nullptr;
}
2019-08-02 19:19:14 +01:00
const auto i = _map.find(emoji.items[0]);
2019-08-01 22:14:19 +01:00
return (i != end(_map)) ? i->second.get() : nullptr;
2019-08-01 15:13:02 +01:00
}
2019-08-02 19:19:14 +01:00
std::shared_ptr<Image> EmojiPack::image(const IsolatedEmoji &emoji) {
const auto i = _images.emplace(emoji, std::weak_ptr<Image>()).first;
if (const auto result = i->second.lock()) {
return result;
}
auto result = std::make_shared<Image>(
std::make_unique<details::ImageSource>(emoji, &_imageLoader));
i->second = result;
return result;
}
2019-08-01 15:13:02 +01:00
void EmojiPack::refresh() {
if (_requestId) {
return;
}
_requestId = _session->api().request(MTPmessages_GetStickerSet(
MTP_inputStickerSetAnimatedEmoji()
)).done([=](const MTPmessages_StickerSet &result) {
2019-08-01 22:14:19 +01:00
_requestId = 0;
2019-08-01 15:13:02 +01:00
refreshDelayed();
result.match([&](const MTPDmessages_stickerSet &data) {
2019-08-01 22:14:19 +01:00
applySet(data);
2019-08-01 15:13:02 +01:00
});
}).fail([=](const RPCError &error) {
2019-08-01 22:14:19 +01:00
_requestId = 0;
2019-08-01 15:13:02 +01:00
refreshDelayed();
}).send();
}
2019-08-01 22:14:19 +01:00
void EmojiPack::applySet(const MTPDmessages_stickerSet &data) {
const auto stickers = collectStickers(data.vdocuments().v);
auto was = base::take(_map);
for (const auto &pack : data.vpacks().v) {
pack.match([&](const MTPDstickerPack &data) {
applyPack(data, stickers);
});
}
for (const auto &[emoji, document] : _map) {
const auto i = was.find(emoji);
if (i == end(was)) {
refreshItems(emoji);
} else {
if (i->second != document) {
refreshItems(i->first);
}
was.erase(i);
}
}
for (const auto &[emoji, Document] : was) {
refreshItems(emoji);
}
}
2019-08-02 19:19:14 +01:00
void EmojiPack::refreshAll() {
for (const auto &[emoji, list] : _items) {
refreshItems(list);
}
}
2019-08-01 22:14:19 +01:00
void EmojiPack::refreshItems(EmojiPtr emoji) {
2019-08-02 19:19:14 +01:00
const auto i = _items.find(IsolatedEmoji{ { emoji } });
2019-08-01 22:14:19 +01:00
if (i == end(_items)) {
return;
}
2019-08-02 19:19:14 +01:00
refreshItems(i->second);
}
void EmojiPack::refreshItems(
const base::flat_set<not_null<HistoryItem*>> &list) {
for (const auto &item : list) {
2019-08-01 22:14:19 +01:00
_session->data().requestItemViewRefresh(item);
}
}
void EmojiPack::applyPack(
const MTPDstickerPack &data,
const base::flat_map<uint64, not_null<DocumentData*>> &map) {
const auto emoji = [&] {
return Ui::Emoji::Find(qs(data.vemoticon()));
}();
const auto document = [&]() -> DocumentData * {
for (const auto &id : data.vdocuments().v) {
const auto i = map.find(id.v);
if (i != end(map)) {
return i->second.get();
}
}
return nullptr;
}();
if (emoji && document) {
_map.emplace_or_assign(emoji, document);
}
}
base::flat_map<uint64, not_null<DocumentData*>> EmojiPack::collectStickers(
const QVector<MTPDocument> &list) const {
auto result = base::flat_map<uint64, not_null<DocumentData*>>();
for (const auto &sticker : list) {
const auto document = _session->data().processDocument(
sticker);
if (document->sticker()) {
result.emplace(document->id, document);
}
}
return result;
}
2019-08-01 15:13:02 +01:00
void EmojiPack::refreshDelayed() {
2019-08-02 19:19:14 +01:00
App::CallDelayed(details::kRefreshTimeout, _session, [=] {
2019-08-01 15:13:02 +01:00
refresh();
});
}
} // namespace Stickers