From a72a31e722044ec84b394bfd4274a462df0e0096 Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Sat, 9 Jan 2016 19:24:16 +0800
Subject: [PATCH] stickers by alt suggestions in mentions dropdown

---
 Telegram/SourceFiles/apiwrap.cpp             |  10 +-
 Telegram/SourceFiles/app.cpp                 |   1 +
 Telegram/SourceFiles/boxes/stickersetbox.cpp |  10 +-
 Telegram/SourceFiles/dropdown.cpp            | 320 +++++++++++++------
 Telegram/SourceFiles/dropdown.h              |  25 +-
 Telegram/SourceFiles/facades.cpp             |  80 +++++
 Telegram/SourceFiles/facades.h               |  26 ++
 Telegram/SourceFiles/gui/emoji_config.h      |  25 +-
 Telegram/SourceFiles/gui/flatinput.cpp       |  16 +-
 Telegram/SourceFiles/gui/flattextarea.cpp    |   2 +-
 Telegram/SourceFiles/gui/text.cpp            |   5 +-
 Telegram/SourceFiles/history.cpp             |  11 +-
 Telegram/SourceFiles/historywidget.cpp       |  39 ++-
 Telegram/SourceFiles/historywidget.h         |   2 +
 Telegram/SourceFiles/localstorage.cpp        |   8 +
 Telegram/SourceFiles/logs.h                  |   8 +-
 Telegram/SourceFiles/main.cpp                |   4 +-
 Telegram/SourceFiles/mainwidget.cpp          |  10 +-
 Telegram/SourceFiles/settings.cpp            |   1 -
 Telegram/SourceFiles/structs.cpp             |   3 +
 Telegram/SourceFiles/types.cpp               |   4 +-
 Telegram/SourceFiles/types.h                 |  10 +-
 22 files changed, 471 insertions(+), 149 deletions(-)

diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp
index 46b95ce1c..6ae7881c1 100644
--- a/Telegram/SourceFiles/apiwrap.cpp
+++ b/Telegram/SourceFiles/apiwrap.cpp
@@ -430,7 +430,7 @@ void ApiWrap::requestBots(ChannelData *peer) {
 
 void ApiWrap::gotChat(PeerData *peer, const MTPmessages_Chats &result) {
 	_peerRequests.remove(peer);
-	
+
 	if (result.type() == mtpc_messages_chats) {
 		const QVector<MTPChat> &v(result.c_messages_chats().vchats.c_vector().v);
 		bool badVersion = false;
@@ -682,10 +682,11 @@ void ApiWrap::requestStickerSets() {
 
 void ApiWrap::gotStickerSet(uint64 setId, const MTPmessages_StickerSet &result) {
 	_stickerSetRequests.remove(setId);
-	
+
 	if (result.type() != mtpc_messages_stickerSet) return;
 	const MTPDmessages_stickerSet &d(result.c_messages_stickerSet());
-	
+	const QVector<MTPStickerPack> &v(d.vpacks.c_vector().v);
+
 	if (d.vset.type() != mtpc_stickerSet) return;
 	const MTPDstickerSet &s(d.vset.c_stickerSet());
 
@@ -731,12 +732,15 @@ void ApiWrap::gotStickerSet(uint64 setId, const MTPmessages_StickerSet &result)
 			++i;
 		}
 	}
+
+	Global::StickersByEmoji_RemovePack(it->stickers);
 	if (pack.isEmpty()) {
 		int32 removeIndex = cStickerSetsOrder().indexOf(setId);
 		if (removeIndex >= 0) cRefStickerSetsOrder().removeAt(removeIndex);
 		sets.erase(it);
 	} else {
 		it->stickers = pack;
+		Global::StickersByEmoji_AddPack(it->stickers);
 	}
 
 	if (writeRecent) {
diff --git a/Telegram/SourceFiles/app.cpp b/Telegram/SourceFiles/app.cpp
index 5898cf1ac..31194084e 100644
--- a/Telegram/SourceFiles/app.cpp
+++ b/Telegram/SourceFiles/app.cpp
@@ -1969,6 +1969,7 @@ namespace App {
 		if (api()) api()->clearWebPageRequests();
 		cSetRecentStickers(RecentStickerPack());
 		cSetStickerSets(StickerSets());
+		Global::SetStickersByEmoji(StickersByEmojiMap());
 		cSetStickerSetsOrder(StickerSetsOrder());
 		cSetLastStickersUpdate(0);
 		cSetSavedGifs(SavedGifs());
diff --git a/Telegram/SourceFiles/boxes/stickersetbox.cpp b/Telegram/SourceFiles/boxes/stickersetbox.cpp
index b47ee602f..b7f62f22b 100644
--- a/Telegram/SourceFiles/boxes/stickersetbox.cpp
+++ b/Telegram/SourceFiles/boxes/stickersetbox.cpp
@@ -50,7 +50,7 @@ void StickerSetInner::gotSet(const MTPmessages_StickerSet &set) {
 		for (int32 i = 0, l = v.size(); i < l; ++i) {
 			DocumentData *doc = App::feedDocument(v.at(i));
 			if (!doc || !doc->sticker()) continue;
-			
+
 			_pack.push_back(doc);
 		}
 		if (d.vset.type() == mtpc_stickerSet) {
@@ -92,6 +92,7 @@ void StickerSetInner::installDone(const MTPBool &result) {
 
 	_setFlags &= ~MTPDstickerSet::flag_disabled;
 	sets.insert(_setId, StickerSet(_setId, _setAccess, _setTitle, _setShortName, _setCount, _setHash, _setFlags)).value().stickers = _pack;
+	Global::StickersByEmoji_AddPack(_pack);
 
 	StickerSetsOrder &order(cRefStickerSetsOrder());
 	int32 insertAtIndex = 0, currentIndex = order.indexOf(_setId);
@@ -507,7 +508,7 @@ void StickersInner::onUpdateSelected() {
 
 float64 StickersInner::aboveShadowOpacity() const {
 	if (_above < 0) return 0;
-	
+
 	int32 dx = 0;
 	int32 dy = qAbs(_above * _rowHeight + _rows.at(_above)->yadd.current() - _started * _rowHeight);
 	return qMin((dx + dy)  * 2. / _rowHeight, 1.);
@@ -613,7 +614,7 @@ void StickersInner::rebuild() {
 	clear();
 	const StickerSetsOrder &order(cStickerSetsOrder());
 	_animStartTimes.reserve(order.size());
-		
+
 	const StickerSets &sets(cStickerSets());
 	for (int32 i = 0, l = order.size(); i < l; ++i) {
 		StickerSets::const_iterator it = sets.constFind(order.at(i));
@@ -867,6 +868,7 @@ void StickersBox::onSave() {
 					if (removeIndex >= 0) cRefStickerSetsOrder().removeAt(removeIndex);
 					sets.erase(it);
 				}
+				Global::StickersByEmoji_RemovePack(it->stickers);
 			}
 		}
 	}
@@ -879,6 +881,7 @@ void StickersBox::onSave() {
 				MTPInputStickerSet setId = (it->id && it->access) ? MTP_inputStickerSetID(MTP_long(it->id), MTP_long(it->access)) : MTP_inputStickerSetShortName(MTP_string(it->shortName));
 				_disenableRequests.insert(MTP::send(MTPmessages_InstallStickerSet(setId, MTP_boolFalse()), rpcDone(&StickersBox::disenableDone), rpcFail(&StickersBox::disenableFail), 0, 5), NullType());
 				it->flags &= ~MTPDstickerSet::flag_disabled;
+				Global::StickersByEmoji_AddPack(it->stickers);
 			}
 			order.push_back(reorder.at(i));
 		}
@@ -887,6 +890,7 @@ void StickersBox::onSave() {
 		if (it->id == CustomStickerSetId || it->id == RecentStickerSetId || order.contains(it->id)) {
 			++it;
 		} else {
+			Global::StickersByEmoji_RemovePack(it->stickers);
 			it = sets.erase(it);
 		}
 	}
diff --git a/Telegram/SourceFiles/dropdown.cpp b/Telegram/SourceFiles/dropdown.cpp
index faa204bfb..df94fa22f 100644
--- a/Telegram/SourceFiles/dropdown.cpp
+++ b/Telegram/SourceFiles/dropdown.cpp
@@ -3564,6 +3564,7 @@ void EmojiPan::onRemoveSetSure() {
 	Ui::hideLayer();
 	StickerSets::iterator it = cRefStickerSets().find(_removingSetId);
 	if (it != cRefStickerSets().cend() && !(it->flags & MTPDstickerSet::flag_official)) {
+		Global::StickersByEmoji_RemovePack(it->stickers);
 		if (it->id && it->access) {
 			MTP::send(MTPmessages_UninstallStickerSet(MTP_inputStickerSetID(MTP_long(it->id), MTP_long(it->access))));
 		} else if (!it->shortName.isEmpty()) {
@@ -3841,24 +3842,74 @@ void EmojiPan::recountContentMaxHeight() {
 	updateContentHeight();
 }
 
-MentionsInner::MentionsInner(MentionsDropdown *parent, MentionRows *mrows, HashtagRows *hrows, BotCommandRows *brows)
+MentionsInner::MentionsInner(MentionsDropdown *parent, MentionRows *mrows, HashtagRows *hrows, BotCommandRows *brows, StickerByEmojiRows *srows)
 : _parent(parent)
 , _mrows(mrows)
 , _hrows(hrows)
 , _brows(brows)
+, _srows(srows)
+, _stickersPerRow(1)
+, _recentInlineBotsInRows(0)
 , _sel(-1)
 , _mouseSel(false)
 , _overDelete(false) {
 }
 
 void MentionsInner::paintEvent(QPaintEvent *e) {
-	QPainter p(this);
+	Painter p(this);
+
+	QRect r(e->rect());
+	if (r != rect()) p.setClipRect(r);
 
 	int32 atwidth = st::mentionFont->width('@'), hashwidth = st::mentionFont->width('#');
 	int32 mentionleft = 2 * st::mentionPadding.left() + st::mentionPhotoSize;
 	int32 mentionwidth = width() - mentionleft - 2 * st::mentionPadding.right();
 	int32 htagleft = st::btnAttachPhoto.width + st::taMsgField.textMrg.left() - st::lineWidth, htagwidth = width() - st::mentionPadding.right() - htagleft - st::mentionScroll.width;
 
+	if (!_srows->isEmpty()) {
+		int32 rows = rowscount(_srows->size(), _stickersPerRow);
+		int32 fromrow = floorclamp(r.y() - st::stickerPanPadding, st::stickerPanSize.height(), 0, rows);
+		int32 torow = ceilclamp(r.y() + r.height() - st::stickerPanPadding, st::stickerPanSize.height(), 0, rows);
+		int32 fromcol = floorclamp(r.x() - st::stickerPanPadding, st::stickerPanSize.width(), 0, _stickersPerRow);
+		int32 tocol = ceilclamp(r.x() + r.width() - st::stickerPanPadding, st::stickerPanSize.width(), 0, _stickersPerRow);
+		for (int32 row = fromrow; row < torow; ++row) {
+			for (int32 col = fromcol; col < tocol; ++col) {
+				int32 index = row * _stickersPerRow + col;
+				if (index >= _srows->size()) break;
+
+				DocumentData *sticker = _srows->at(index);
+				if (!sticker->sticker()) continue;
+
+				QPoint pos(st::stickerPanPadding + col * st::stickerPanSize.width(), st::stickerPanPadding + row * st::stickerPanSize.height());
+				if (_sel == index) {
+					QPoint tl(pos);
+					if (rtl()) tl.setX(width() - tl.x() - st::stickerPanSize.width());
+					App::roundRect(p, QRect(tl, st::stickerPanSize), st::emojiPanHover, StickerHoverCorners);
+				}
+
+				bool goodThumb = !sticker->thumb->isNull() && ((sticker->thumb->width() >= 128) || (sticker->thumb->height() >= 128));
+				if (goodThumb) {
+					sticker->thumb->load();
+				} else {
+					sticker->checkSticker();
+				}
+
+				float64 coef = qMin((st::stickerPanSize.width() - st::msgRadius * 2) / float64(sticker->dimensions.width()), (st::stickerPanSize.height() - st::msgRadius * 2) / float64(sticker->dimensions.height()));
+				if (coef > 1) coef = 1;
+				int32 w = qRound(coef * sticker->dimensions.width()), h = qRound(coef * sticker->dimensions.height());
+				if (w < 1) w = 1;
+				if (h < 1) h = 1;
+				QPoint ppos = pos + QPoint((st::stickerPanSize.width() - w) / 2, (st::stickerPanSize.height() - h) / 2);
+				if (goodThumb) {
+					p.drawPixmapLeft(ppos, width(), sticker->thumb->pix(w, h));
+				} else if (!sticker->sticker()->img->isNull()) {
+					p.drawPixmapLeft(ppos, width(), sticker->sticker()->img->pix(w, h));
+				}
+			}
+		}
+		return;
+	}
+
 	int32 from = qFloor(e->rect().top() / st::mentionHeight), to = qFloor(e->rect().bottom() / st::mentionHeight) + 1;
 	int32 last = _mrows->isEmpty() ? (_hrows->isEmpty() ? _brows->size() : _hrows->size()) : _mrows->size();
 	bool hasUsername = _parent->filter().indexOf('@') > 1;
@@ -3970,6 +4021,10 @@ void MentionsInner::paintEvent(QPaintEvent *e) {
 	p.fillRect(cWideMode() ? st::lineWidth : 0, _parent->innerBottom() - st::lineWidth, width() - (cWideMode() ? st::lineWidth : 0), st::lineWidth, st::shadowColor->b);
 }
 
+void MentionsInner::resizeEvent(QResizeEvent *e) {
+	_stickersPerRow = int32(width() - 2 * st::stickerPanPadding) / int32(st::stickerPanSize.width());
+}
+
 void MentionsInner::mouseMoveEvent(QMouseEvent *e) {
 	_mousePos = mapToGlobal(e->pos());
 	_mouseSel = true;
@@ -3978,29 +4033,47 @@ void MentionsInner::mouseMoveEvent(QMouseEvent *e) {
 
 void MentionsInner::clearSel() {
 	_mouseSel = _overDelete = false;
-	setSel((_mrows->isEmpty() && _brows->isEmpty() && _hrows->isEmpty()) ? -1 : 0);
+	setSel((_mrows->isEmpty() && _brows->isEmpty() && _hrows->isEmpty() && _srows->isEmpty()) ? -1 : 0);
 }
 
-bool MentionsInner::moveSel(int direction) {
+bool MentionsInner::moveSel(int key) {
 	_mouseSel = false;
-	int32 maxSel = (_mrows->isEmpty() ? (_hrows->isEmpty() ? _brows->size() : _hrows->size()) : _mrows->size());
+	int32 maxSel = (_mrows->isEmpty() ? (_hrows->isEmpty() ? (_brows->isEmpty() ? _srows->size() : _brows->size()) : _hrows->size()) : _mrows->size());
+	int32 direction = (key == Qt::Key_Up) ? -1 : (key == Qt::Key_Down ? 1 : 0);
+	if (!_srows->isEmpty()) {
+		if (key == Qt::Key_Left) {
+			direction = -1;
+		} else if (key == Qt::Key_Right) {
+			direction = 1;
+		} else {
+			direction *= _stickersPerRow;
+		}
+	}
 	if (_sel >= maxSel || _sel < 0) {
-		if (direction < 0) {
+		if (direction < -1) {
+			setSel(((maxSel - 1) / _stickersPerRow) * _stickersPerRow, true);
+		} else if (direction < 0) {
 			setSel(maxSel - 1, true);
 		} else {
 			setSel(0, true);
 		}
 		return (_sel >= 0 && _sel < maxSel);
 	}
-	setSel((_sel + direction >= maxSel) ? -1 : (_sel + direction), true);
+	setSel((_sel + direction >= maxSel || _sel + direction < 0) ? -1 : (_sel + direction), true);
 	return true;
 }
 
 bool MentionsInner::select() {
-	QString sel = getSelected();
-	if (!sel.isEmpty()) {
-		emit chosen(sel);
-		return true;
+	if (!_srows->isEmpty()) {
+		if (_sel >= 0 && _sel < _srows->size()) {
+			emit selected(_srows->at(_sel));
+		}
+	} else {
+		QString sel = getSelected();
+		if (!sel.isEmpty()) {
+			emit chosen(sel);
+			return true;
+		}
 	}
 	return false;
 }
@@ -4086,21 +4159,51 @@ void MentionsInner::leaveEvent(QEvent *e) {
 	}
 }
 
+void MentionsInner::updateSelectedRow() {
+	if (_sel >= 0) {
+		if (_srows->isEmpty()) {
+			update(0, _sel * st::mentionHeight, width(), st::mentionHeight);
+		} else {
+			int32 row = _sel / _stickersPerRow, col = _sel % _stickersPerRow;
+			update(st::stickerPanPadding + col * st::stickerPanSize.width(), st::stickerPanPadding + row * st::stickerPanSize.height(), st::stickerPanSize.width(), st::stickerPanSize.height());
+		}
+	}
+}
+
 void MentionsInner::setSel(int sel, bool scroll) {
-	if (_sel >= 0) update(0, _sel * st::mentionHeight, width(), st::mentionHeight);
+	updateSelectedRow();
 	_sel = sel;
-	if (_sel >= 0) update(0, _sel * st::mentionHeight, width(), st::mentionHeight);
-	int32 maxSel = _mrows->isEmpty() ? (_hrows->isEmpty() ? _brows->size() : _hrows->size()) : _mrows->size();
-	if (scroll && _sel >= 0 && _sel < maxSel) emit mustScrollTo(_sel * st::mentionHeight, (_sel + 1) * st::mentionHeight);
+	updateSelectedRow();
+
+	if (scroll && _sel >= 0) {
+		if (_srows->isEmpty()) {
+			emit mustScrollTo(_sel * st::mentionHeight, (_sel + 1) * st::mentionHeight);
+		} else {
+			int32 row = _sel / _stickersPerRow;
+			emit mustScrollTo(st::stickerPanPadding + row * st::stickerPanSize.height(), st::stickerPanPadding + (row + 1) * st::stickerPanSize.height());
+		}
+	}
 }
 
 void MentionsInner::onUpdateSelected(bool force) {
 	QPoint mouse(mapFromGlobal(_mousePos));
 	if ((!force && !rect().contains(mouse)) || !_mouseSel) return;
 
-	int w = width(), mouseY = mouse.y();
-	int32 sel = mouseY / int32(st::mentionHeight), maxSel = _mrows->isEmpty() ? (_hrows->isEmpty() ? _brows->size() : _hrows->size()) : _mrows->size();
-	_overDelete = (!_hrows->isEmpty() || (!_mrows->isEmpty() && sel < _recentInlineBotsInRows)) ? (mouse.x() >= w - st::mentionHeight) : false;
+	int32 sel = -1, maxSel = 0;
+	if (!_srows->isEmpty()) {
+		int32 rows = rowscount(_srows->size(), _stickersPerRow);
+		int32 row = (mouse.y() >= st::stickerPanPadding) ? ((mouse.y() - st::stickerPanPadding) / st::stickerPanSize.height()) : -1;
+		int32 col = (mouse.x() >= st::stickerPanPadding) ? ((mouse.x() - st::stickerPanPadding) / st::stickerPanSize.width()) : -1;
+		if (row >= 0 && col >= 0) {
+			sel = row * _stickersPerRow + col;
+		}
+		maxSel = _srows->size();
+		_overDelete = false;
+	} else {
+		sel = mouse.y() / int32(st::mentionHeight);
+		maxSel = _mrows->isEmpty() ? (_hrows->isEmpty() ? _brows->size() : _hrows->size()) : _mrows->size();
+		_overDelete = (!_hrows->isEmpty() || (!_mrows->isEmpty() && sel < _recentInlineBotsInRows)) ? (mouse.x() >= width() - st::mentionHeight) : false;
+	}
 	if (sel < 0 || sel >= maxSel) {
 		sel = -1;
 	}
@@ -4119,7 +4222,7 @@ void MentionsInner::onParentGeometryChanged() {
 
 MentionsDropdown::MentionsDropdown(QWidget *parent) : TWidget(parent)
 , _scroll(this, st::mentionScroll)
-, _inner(this, &_mrows, &_hrows, &_brows)
+, _inner(this, &_mrows, &_hrows, &_brows, &_srows)
 , _chat(0)
 , _user(0)
 , _channel(0)
@@ -4130,6 +4233,7 @@ MentionsDropdown::MentionsDropdown(QWidget *parent) : TWidget(parent)
 	_hideTimer.setSingleShot(true);
 	connect(&_hideTimer, SIGNAL(timeout()), this, SLOT(hideStart()));
 	connect(&_inner, SIGNAL(chosen(QString)), this, SIGNAL(chosen(QString)));
+	connect(&_inner, SIGNAL(selected(DocumentData*)), this, SIGNAL(stickerSelected(DocumentData*)));
 	connect(&_inner, SIGNAL(mustScrollTo(int,int)), &_scroll, SLOT(scrollToY(int,int)));
 
 	connect(App::wnd(), SIGNAL(imageLoaded()), &_inner, SLOT(update()));
@@ -4150,7 +4254,7 @@ MentionsDropdown::MentionsDropdown(QWidget *parent) : TWidget(parent)
 }
 
 void MentionsDropdown::paintEvent(QPaintEvent *e) {
-	QPainter p(this);
+	Painter p(this);
 
 	if (_a_appearance.animating()) {
 		p.setOpacity(a_opacity.current());
@@ -4158,29 +4262,43 @@ void MentionsDropdown::paintEvent(QPaintEvent *e) {
 		return;
 	}
 
-	p.fillRect(rect(), st::white->b);
-
+	p.fillRect(rect(), st::white);
 }
 
 void MentionsDropdown::showFiltered(PeerData *peer, QString query, bool start) {
-	if (query.isEmpty() || (peer->isUser() && query.at(0) == '@' && (!start || cRecentInlineBots().isEmpty()))) {
-		if (!isHidden()) {
-			hideStart();
-		}
-		return;
-	}
-
 	_chat = peer->asChat();
 	_user = peer->asUser();
 	_channel = peer->asChannel();
+	if (query.isEmpty()) {
+		rowsUpdated(MentionRows(), HashtagRows(), BotCommandRows(), _srows, false);
+		return;
+	}
+
+	_emoji = EmojiPtr();
+
 	query = query.toLower();
-	bool toDown = (_filter != query);
-	if (toDown) {
+	bool resetScroll = (_filter != query);
+	if (resetScroll) {
 		_filter = query;
 	}
 	_addInlineBots = start;
 
-	updateFiltered(toDown);
+	updateFiltered(resetScroll);
+}
+
+void MentionsDropdown::showStickers(EmojiPtr emoji) {
+	bool resetScroll = (_emoji != emoji);
+	_emoji = emoji;
+	if (!emoji) {
+		rowsUpdated(_mrows, _hrows, _brows, StickerByEmojiRows(), false);
+		return;
+	}
+
+	_chat = 0;
+	_user = 0;
+	_channel = 0;
+
+	updateFiltered(resetScroll);
 }
 
 bool MentionsDropdown::clearFilteredBotCommands() {
@@ -4189,12 +4307,22 @@ bool MentionsDropdown::clearFilteredBotCommands() {
 	return true;
 }
 
-void MentionsDropdown::updateFiltered(bool toDown) {
+void MentionsDropdown::updateFiltered(bool resetScroll) {
 	int32 now = unixtime(), recentInlineBots = 0;
 	MentionRows mrows;
 	HashtagRows hrows;
 	BotCommandRows brows;
-	if (_filter.at(0) == '@') {
+	StickerByEmojiRows srows;
+	if (_emoji) {
+		const StickersByEmojiMap &stickers(Global::StickersByEmoji());
+		StickersByEmojiMap::const_iterator it = stickers.constFind(emojiGetNoColor(_emoji));
+		if (it != stickers.cend() && !it->isEmpty()) {
+			srows.reserve(it->size());
+			for (StickersByEmojiList::const_iterator i = it->cbegin(), e = it->cend(); i != e; ++i) {
+				srows.push_back(i.key());
+			}
+		}
+	} else if (_filter.at(0) == '@') {
 		if (_chat) {
 			mrows.reserve((_addInlineBots ? cRecentInlineBots().size() : 0) + (_chat->participants.isEmpty() ? _chat->lastAuthors.size() : _chat->participants.size()));
 		} else if (_channel && _channel->isMegagroup()) {
@@ -4214,46 +4342,46 @@ void MentionsDropdown::updateFiltered(bool toDown) {
 				++recentInlineBots;
 			}
 		}
-	}
-	if (_filter.at(0) == '@' && _chat) {
-		QMultiMap<int32, UserData*> ordered;
-		mrows.reserve(mrows.size() + (_chat->participants.isEmpty() ? _chat->lastAuthors.size() : _chat->participants.size()));
-		if (_chat->noParticipantInfo()) {
-			if (App::api()) App::api()->requestFullPeer(_chat);
-		} else if (!_chat->participants.isEmpty()) {
-			for (ChatData::Participants::const_iterator i = _chat->participants.cbegin(), e = _chat->participants.cend(); i != e; ++i) {
-				UserData *user = i.key();
-				if (user->username.isEmpty()) continue;
-				if (_filter.size() > 1 && (!user->username.startsWith(_filter.midRef(1), Qt::CaseInsensitive) || user->username.size() + 1 == _filter.size())) continue;
-				ordered.insertMulti(App::onlineForSort(user, now), user);
+		if (_chat) {
+			QMultiMap<int32, UserData*> ordered;
+			mrows.reserve(mrows.size() + (_chat->participants.isEmpty() ? _chat->lastAuthors.size() : _chat->participants.size()));
+			if (_chat->noParticipantInfo()) {
+				if (App::api()) App::api()->requestFullPeer(_chat);
+			} else if (!_chat->participants.isEmpty()) {
+				for (ChatData::Participants::const_iterator i = _chat->participants.cbegin(), e = _chat->participants.cend(); i != e; ++i) {
+					UserData *user = i.key();
+					if (user->username.isEmpty()) continue;
+					if (_filter.size() > 1 && (!user->username.startsWith(_filter.midRef(1), Qt::CaseInsensitive) || user->username.size() + 1 == _filter.size())) continue;
+					ordered.insertMulti(App::onlineForSort(user, now), user);
+				}
 			}
-		}
-		for (MentionRows::const_iterator i = _chat->lastAuthors.cbegin(), e = _chat->lastAuthors.cend(); i != e; ++i) {
-			UserData *user = *i;
-			if (user->username.isEmpty()) continue;
-			if (_filter.size() > 1 && (!user->username.startsWith(_filter.midRef(1), Qt::CaseInsensitive) || user->username.size() + 1 == _filter.size())) continue;
-			mrows.push_back(user);
-			if (!ordered.isEmpty()) {
-				ordered.remove(App::onlineForSort(user, now), user);
-			}
-		}
-		if (!ordered.isEmpty()) {
-			for (QMultiMap<int32, UserData*>::const_iterator i = ordered.cend(), b = ordered.cbegin(); i != b;) {
-				--i;
-				mrows.push_back(i.value());
-			}
-		}
-	} else if (_filter.at(0) == '@' && _channel && _channel->isMegagroup()) {
-		QMultiMap<int32, UserData*> ordered;
-		if (_channel->mgInfo->lastParticipants.isEmpty() || _channel->lastParticipantsCountOutdated()) {
-			if (App::api()) App::api()->requestLastParticipants(_channel);
-		} else {
-			mrows.reserve(mrows.size() + _channel->mgInfo->lastParticipants.size());
-			for (MegagroupInfo::LastParticipants::const_iterator i = _channel->mgInfo->lastParticipants.cbegin(), e = _channel->mgInfo->lastParticipants.cend(); i != e; ++i) {
+			for (MentionRows::const_iterator i = _chat->lastAuthors.cbegin(), e = _chat->lastAuthors.cend(); i != e; ++i) {
 				UserData *user = *i;
 				if (user->username.isEmpty()) continue;
 				if (_filter.size() > 1 && (!user->username.startsWith(_filter.midRef(1), Qt::CaseInsensitive) || user->username.size() + 1 == _filter.size())) continue;
 				mrows.push_back(user);
+				if (!ordered.isEmpty()) {
+					ordered.remove(App::onlineForSort(user, now), user);
+				}
+			}
+			if (!ordered.isEmpty()) {
+				for (QMultiMap<int32, UserData*>::const_iterator i = ordered.cend(), b = ordered.cbegin(); i != b;) {
+					--i;
+					mrows.push_back(i.value());
+				}
+			}
+		} else if (_channel && _channel->isMegagroup()) {
+			QMultiMap<int32, UserData*> ordered;
+			if (_channel->mgInfo->lastParticipants.isEmpty() || _channel->lastParticipantsCountOutdated()) {
+				if (App::api()) App::api()->requestLastParticipants(_channel);
+			} else {
+				mrows.reserve(mrows.size() + _channel->mgInfo->lastParticipants.size());
+				for (MegagroupInfo::LastParticipants::const_iterator i = _channel->mgInfo->lastParticipants.cbegin(), e = _channel->mgInfo->lastParticipants.cend(); i != e; ++i) {
+					UserData *user = *i;
+					if (user->username.isEmpty()) continue;
+					if (_filter.size() > 1 && (!user->username.startsWith(_filter.midRef(1), Qt::CaseInsensitive) || user->username.size() + 1 == _filter.size())) continue;
+					mrows.push_back(user);
+				}
 			}
 		}
 	} else if (_filter.at(0) == '#') {
@@ -4332,28 +4460,31 @@ void MentionsDropdown::updateFiltered(bool toDown) {
 			}
 		}
 	}
-	rowsUpdated(mrows, hrows, brows, toDown);
+	rowsUpdated(mrows, hrows, brows, srows, resetScroll);
 	_inner.setRecentInlineBotsInRows(recentInlineBots);
 }
 
-void MentionsDropdown::rowsUpdated(const MentionRows &mrows, const HashtagRows &hrows, const BotCommandRows &brows, bool toDown) {
-	if (mrows.isEmpty() && hrows.isEmpty() && brows.isEmpty()) {
+void MentionsDropdown::rowsUpdated(const MentionRows &mrows, const HashtagRows &hrows, const BotCommandRows &brows, const StickerByEmojiRows &srows, bool resetScroll) {
+	if (mrows.isEmpty() && hrows.isEmpty() && brows.isEmpty() && srows.isEmpty()) {
 		if (!isHidden()) {
 			hideStart();
 		}
 		_mrows.clear();
 		_hrows.clear();
 		_brows.clear();
+		_srows.clear();
 	} else {
 		_mrows = mrows;
 		_hrows = hrows;
 		_brows = brows;
+		_srows = srows;
+
 		bool hidden = _hiding || isHidden();
 		if (hidden) {
 			show();
 			_scroll.show();
 		}
-		recount(toDown);
+		recount(resetScroll);
 		if (hidden) {
 			hide();
 			showStart();
@@ -4363,31 +4494,37 @@ void MentionsDropdown::rowsUpdated(const MentionRows &mrows, const HashtagRows &
 
 void MentionsDropdown::setBoundings(QRect boundings) {
 	_boundings = boundings;
-	resize(_boundings.width(), height());
-	_scroll.resize(size());
-	_inner.resize(width(), _inner.height());
 	recount();
 }
 
-void MentionsDropdown::recount(bool toDown) {
-	int32 h = (_mrows.isEmpty() ? (_hrows.isEmpty() ? _brows.size() : _hrows.size()) : _mrows.size()) * st::mentionHeight, oldst = _scroll.scrollTop(), st = oldst;
+void MentionsDropdown::recount(bool resetScroll) {
+	int32 h = 0, oldst = _scroll.scrollTop(), st = oldst, maxh = 4.5 * st::mentionHeight;
+	if (!_srows.isEmpty()) {
+		int32 stickersPerRow = int32(_boundings.width() - 2 * st::stickerPanPadding) / int32(st::stickerPanSize.width());
+		int32 rows = rowscount(_srows.size(), stickersPerRow);
+		h = st::stickerPanPadding + rows * st::stickerPanSize.height();
+	} else if (!_mrows.isEmpty()) {
+		h = _mrows.size() * st::mentionHeight;
+	} else if (!_hrows.isEmpty()) {
+		h = _hrows.size() * st::mentionHeight;
+	} else if (!_brows.isEmpty()) {
+		h = _brows.size() * st::mentionHeight;
+	}
 
-	if (_inner.height() != h) {
-//		st += h - _inner.height();
-		_inner.resize(width(), h);
+	if (_inner.width() != _boundings.width() || _inner.height() != h) {
+		_inner.resize(_boundings.width(), h);
 	}
 	if (h > _boundings.height()) h = _boundings.height();
-	if (h > 4.5 * st::mentionHeight) h = 4.5 * st::mentionHeight;
-	if (height() != h) {
-//		st += _scroll.height() - h;
-		setGeometry(0, _boundings.height() - h, width(), h);
-		_scroll.resize(width(), h);
+	if (h > maxh) h = maxh;
+	if (width() != _boundings.width() || height() != h) {
+		setGeometry(0, _boundings.height() - h, _boundings.width(), h);
+		_scroll.resize(_boundings.width(), h);
 	} else if (y() != _boundings.height() - h) {
 		move(0, _boundings.height() - h);
 	}
-	if (toDown) st = 0;// _scroll.scrollTopMax();
+	if (resetScroll) st = 0;
 	if (st != oldst) _scroll.scrollToY(st);
-	if (toDown) _inner.clearSel();
+	if (resetScroll) _inner.clearSel();
 }
 
 void MentionsDropdown::fastHide() {
@@ -4487,11 +4624,8 @@ bool MentionsDropdown::eventFilter(QObject *obj, QEvent *e) {
 	if (isHidden()) return QWidget::eventFilter(obj, e);
 	if (e->type() == QEvent::KeyPress) {
 		QKeyEvent *ev = static_cast<QKeyEvent*>(e);
-		if (ev->key() == Qt::Key_Up) {
-			_inner.moveSel(-1);
-			return true;
-		} else if (ev->key() == Qt::Key_Down) {
-			return _inner.moveSel(1);
+		if (ev->key() == Qt::Key_Up || ev->key() == Qt::Key_Down || (!_srows.isEmpty() && (ev->key() == Qt::Key_Left || ev->key() == Qt::Key_Right))) {
+			return _inner.moveSel(ev->key());
 		} else if (ev->key() == Qt::Key_Enter || ev->key() == Qt::Key_Return) {
 			return _inner.select();
 		}
diff --git a/Telegram/SourceFiles/dropdown.h b/Telegram/SourceFiles/dropdown.h
index b0cac7d40..e6dcd1d3d 100644
--- a/Telegram/SourceFiles/dropdown.h
+++ b/Telegram/SourceFiles/dropdown.h
@@ -727,6 +727,7 @@ private:
 typedef QList<UserData*> MentionRows;
 typedef QList<QString> HashtagRows;
 typedef QList<QPair<UserData*, const BotCommand*> > BotCommandRows;
+typedef QList<DocumentData*> StickerByEmojiRows;
 
 class MentionsDropdown;
 class MentionsInner : public TWidget {
@@ -734,9 +735,10 @@ class MentionsInner : public TWidget {
 
 public:
 
-	MentionsInner(MentionsDropdown *parent, MentionRows *mrows, HashtagRows *hrows, BotCommandRows *brows);
+	MentionsInner(MentionsDropdown *parent, MentionRows *mrows, HashtagRows *hrows, BotCommandRows *brows, StickerByEmojiRows *srows);
 
 	void paintEvent(QPaintEvent *e);
+	void resizeEvent(QResizeEvent *e);
 
 	void enterEvent(QEvent *e);
 	void leaveEvent(QEvent *e);
@@ -745,7 +747,7 @@ public:
 	void mouseMoveEvent(QMouseEvent *e);
 
 	void clearSel();
-	bool moveSel(int direction);
+	bool moveSel(int key);
 	bool select();
 
 	void setRecentInlineBotsInRows(int32 bots);
@@ -755,6 +757,7 @@ public:
 signals:
 
 	void chosen(QString mentionOrHashtag);
+	void selected(DocumentData *sticker);
 	void mustScrollTo(int scrollToTop, int scrollToBottom);
 
 public slots:
@@ -764,13 +767,15 @@ public slots:
 
 private:
 
+	void updateSelectedRow();
 	void setSel(int sel, bool scroll = false);
 
 	MentionsDropdown *_parent;
 	MentionRows *_mrows;
 	HashtagRows *_hrows;
 	BotCommandRows *_brows;
-	int32 _recentInlineBotsInRows;
+	StickerByEmojiRows *_srows;
+	int32 _stickersPerRow, _recentInlineBotsInRows;
 	int32 _sel;
 	bool _mouseSel;
 	QPoint _mousePos;
@@ -791,7 +796,8 @@ public:
 
 	bool clearFilteredBotCommands();
 	void showFiltered(PeerData *peer, QString query, bool start);
-	void updateFiltered(bool toDown = false);
+	void showStickers(EmojiPtr emoji);
+	void updateFiltered(bool resetScroll = false);
 	void setBoundings(QRect boundings);
 
 	void step_appearance(float64 ms, bool timer);
@@ -807,6 +813,10 @@ public:
 	bool eventFilter(QObject *obj, QEvent *e);
 	QString getSelected() const;
 
+	bool stickersShown() const {
+		return !_srows.isEmpty();
+	}
+
 	bool overlaps(const QRect &globalRect) {
 		if (isHidden() || !testAttribute(Qt::WA_OpaquePaintEvent)) return false;
 
@@ -818,6 +828,7 @@ public:
 signals:
 
 	void chosen(QString mentionOrHashtag);
+	void stickerSelected(DocumentData *sticker);
 
 public slots:
 
@@ -828,14 +839,15 @@ public slots:
 
 private:
 
-	void recount(bool toDown = false);
+	void recount(bool resetScroll = false);
 
 	QPixmap _cache;
 	MentionRows _mrows;
 	HashtagRows _hrows;
 	BotCommandRows _brows;
+	StickerByEmojiRows _srows;
 
-	void rowsUpdated(const MentionRows &mrows, const HashtagRows &hrows, const BotCommandRows &brows, bool toDown);
+	void rowsUpdated(const MentionRows &mrows, const HashtagRows &hrows, const BotCommandRows &brows, const StickerByEmojiRows &srows, bool resetScroll);
 
 	ScrollArea _scroll;
 	MentionsInner _inner;
@@ -843,6 +855,7 @@ private:
 	ChatData *_chat;
 	UserData *_user;
 	ChannelData *_channel;
+	EmojiPtr _emoji;
 	QString _filter;
 	QRect _boundings;
 	bool _addInlineBots;
diff --git a/Telegram/SourceFiles/facades.cpp b/Telegram/SourceFiles/facades.cpp
index 09b132740..bdfd52eea 100644
--- a/Telegram/SourceFiles/facades.cpp
+++ b/Telegram/SourceFiles/facades.cpp
@@ -174,3 +174,83 @@ namespace Notify {
 	}
 
 }
+
+namespace Global {
+
+	struct Data {
+		uint64 LaunchId;
+
+		StickersByEmojiMap StickersByEmoji;
+	};
+
+	Data *_data = 0;
+
+	Initializer::Initializer() {
+		initThirdParty();
+		_data = new Data();
+		memset_rand(&_data->LaunchId, sizeof(_data->LaunchId));
+	}
+
+	Initializer::~Initializer() {
+		deinitThirdParty();
+	}
+
+#define DefineGlobalReadOnly(Type, Name) const Type &Name() { \
+	t_assert_full(_data != 0, "_data is null in Global::" #Name, __FILE__, __LINE__); \
+	return _data->Name; \
+}
+#define DefineGlobal(Type, Name) DefineGlobalReadOnly(Type, Name) \
+void Set##Name(const Type &Name) { \
+	t_assert_full(_data != 0, "_data is null in Global::Set" #Name, __FILE__, __LINE__); \
+	_data->Name = Name; \
+} \
+Type &Ref##Name() { \
+	t_assert_full(_data != 0, "_data is null in Global::Ref" #Name, __FILE__, __LINE__); \
+	return _data->Name; \
+}
+
+	DefineGlobalReadOnly(uint64, LaunchId);
+
+	DefineGlobal(StickersByEmojiMap, StickersByEmoji);
+
+	void StickersByEmoji_Add(DocumentData *doc) {
+		if (StickerData *sticker = doc->sticker()) {
+			if (EmojiPtr emoji = emojiGetNoColor(emojiFromText(sticker->alt))) {
+				RefStickersByEmoji()[emoji].insert(doc);
+			}
+		}
+	}
+
+	bool StickersByEmoji_Remove(DocumentData *doc) {
+		if (StickerData *sticker = doc->sticker()) {
+			if (EmojiPtr emoji = emojiGetNoColor(emojiFromText(sticker->alt))) {
+				StickersByEmojiMap stickers(RefStickersByEmoji());
+				StickersByEmojiMap::iterator iList = stickers.find(emoji);
+				if (iList != stickers.cend()) {
+					StickersByEmojiList::iterator iEntry = iList->find(doc);
+					if (iEntry != iList->cend()) {
+						iList->erase(iEntry);
+						if (iList->isEmpty()) {
+							stickers.erase(iList);
+						}
+						return true;
+					}
+				}
+			}
+		}
+		return false;
+	}
+
+	void StickersByEmoji_AddPack(const StickerPack &pack) {
+		for (StickerPack::const_iterator i = pack.cbegin(), e = pack.cend(); i != e; ++i) {
+			StickersByEmoji_Add(*i);
+		}
+	}
+
+	void StickersByEmoji_RemovePack(const StickerPack &pack) {
+		for (StickerPack::const_iterator i = pack.cbegin(), e = pack.cend(); i != e; ++i) {
+			StickersByEmoji_Remove(*i);
+		}
+	}
+
+};
diff --git a/Telegram/SourceFiles/facades.h b/Telegram/SourceFiles/facades.h
index ed7ee718c..14db6d2d1 100644
--- a/Telegram/SourceFiles/facades.h
+++ b/Telegram/SourceFiles/facades.h
@@ -95,3 +95,29 @@ namespace Notify {
 	void automaticLoadSettingsChangedGif();
 
 };
+
+typedef OrderedSet<DocumentData*> StickersByEmojiList;
+typedef QMap<EmojiPtr, StickersByEmojiList> StickersByEmojiMap;
+
+namespace Global {
+
+	class Initializer {
+	public:
+		Initializer();
+		~Initializer();
+	};
+
+#define DeclareGlobalReadOnly(Type, Name) const Type &Name();
+#define DeclareGlobal(Type, Name) DeclareGlobalReadOnly(Type, Name) \
+	void Set##Name(const Type &Name); \
+	Type &Ref##Name();
+
+	DeclareGlobalReadOnly(uint64, LaunchId);
+
+	DeclareGlobal(StickersByEmojiMap, StickersByEmoji);
+	void StickersByEmoji_Add(DocumentData *doc);
+	bool StickersByEmoji_Remove(DocumentData *doc);
+	void StickersByEmoji_AddPack(const StickerPack &pack);
+	void StickersByEmoji_RemovePack(const StickerPack &pack);
+
+};
diff --git a/Telegram/SourceFiles/gui/emoji_config.h b/Telegram/SourceFiles/gui/emoji_config.h
index e377374ba..1734d3d82 100644
--- a/Telegram/SourceFiles/gui/emoji_config.h
+++ b/Telegram/SourceFiles/gui/emoji_config.h
@@ -83,7 +83,7 @@ inline EmojiPtr emojiFromUrl(const QString &url) {
 	return emojiFromKey(url.midRef(10).toULongLong(0, 16)); // skip emoji://e.
 }
 
-inline EmojiPtr emojiFromText(const QChar *ch, const QChar *e, int &len) {
+inline EmojiPtr emojiFromText(const QChar *ch, const QChar *e, int *plen = 0) {
 	EmojiPtr emoji = 0;
 	if (ch + 1 < e && ((ch->isHighSurrogate() && (ch + 1)->isLowSurrogate()) || (((ch->unicode() >= 0x30 && ch->unicode() < 0x3A) || ch->unicode() == 0x23 || ch->unicode() == 0x2A) && (ch + 1)->unicode() == 0x20E3))) {
 		uint32 code = (ch->unicode() << 16) | (ch + 1)->unicode();
@@ -108,15 +108,15 @@ inline EmojiPtr emojiFromText(const QChar *ch, const QChar *e, int &len) {
 	} else if (ch + 2 < e && ((ch->unicode() >= 0x30 && ch->unicode() < 0x3A) || ch->unicode() == 0x23 || ch->unicode() == 0x2A) && (ch + 1)->unicode() == 0xFE0F && (ch + 2)->unicode() == 0x20E3) {
 		uint32 code = (ch->unicode() << 16) | (ch + 2)->unicode();
 		emoji = emojiGet(code);
-		len = emoji->len + 1;
+		if (plen) *plen = emoji->len + 1;
 		return emoji;
 	} else if (ch < e) {
 		emoji = emojiGet(ch->unicode());
-		Q_ASSERT(emoji != TwoSymbolEmoji);
+		t_assert(emoji != TwoSymbolEmoji);
 	}
 
 	if (emoji) {
-		len = emoji->len + ((ch + emoji->len < e && (ch + emoji->len)->unicode() == 0xFE0F) ? 1 : 0);
+		int32 len = emoji->len + ((ch + emoji->len < e && (ch + emoji->len)->unicode() == 0xFE0F) ? 1 : 0);
 		if (emoji->color && (ch + len + 1 < e && (ch + len)->isHighSurrogate() && (ch + len + 1)->isLowSurrogate())) { // color
 			uint32 color = ((uint32((ch + len)->unicode()) << 16) | uint32((ch + len + 1)->unicode()));
 			EmojiPtr col = emojiGet(emoji, color);
@@ -128,8 +128,21 @@ inline EmojiPtr emojiFromText(const QChar *ch, const QChar *e, int &len) {
 				}
 			}
 		}
+		if (plen) *plen = len;
+	}
+
+	return emoji;
+}
+
+inline EmojiPtr emojiFromText(const QString &text, int32 *plen = 0) {
+	return text.isEmpty() ? EmojiPtr(0) : emojiFromText(text.constBegin(), text.constEnd(), plen);
+}
+
+inline EmojiPtr emojiGetNoColor(EmojiPtr emoji) {
+	if (emoji && emoji->color && (emoji->color & 0xFFFF0000U) != 0xFFFF0000U) {
+		EmojiPtr result = emojiGet(emoji->code);
+		return (result == TwoSymbolEmoji) ? emojiGet(emoji->code, emoji->code2) : result;
 	}
-	
 	return emoji;
 }
 
@@ -180,7 +193,7 @@ inline QString replaceEmojis(const QString &text, EntitiesInText &entities) {
 		if (canFindEmoji) {
 			emojiFind(ch, e, newEmojiEnd, emojiCode);
 		}
-		
+
 		while (currentEntity < entitiesCount && ch >= emojiStart + entities[currentEntity].offset + entities[currentEntity].length) {
 			++currentEntity;
 		}
diff --git a/Telegram/SourceFiles/gui/flatinput.cpp b/Telegram/SourceFiles/gui/flatinput.cpp
index 5d0a25ace..a7f4241a0 100644
--- a/Telegram/SourceFiles/gui/flatinput.cpp
+++ b/Telegram/SourceFiles/gui/flatinput.cpp
@@ -66,7 +66,7 @@ FlatInput::FlatInput(QWidget *parent, const style::flatInput &st, const QString
 , _notingBene(0)
 , _st(st) {
 	resize(_st.width, _st.height);
-	
+
 	setFont(_st.font->f);
 	setAlignment(_st.align);
 
@@ -959,7 +959,7 @@ void InputArea::processDocumentContentsChange(int position, int charsAdded) {
 				const QChar *ch = t.constData(), *e = ch + t.size();
 				for (; ch != e; ++ch, ++fp) {
 					int32 emojiLen = 0;
-					emoji = emojiFromText(ch, e, emojiLen);
+					emoji = emojiFromText(ch, e, &emojiLen);
 					if (emoji) {
 						if (replacePosition >= 0) {
 							emoji = 0; // replace tilde char format first
@@ -1331,7 +1331,7 @@ InputField::InputField(QWidget *parent, const style::InputField &st, const QStri
 	connect(&_inner, SIGNAL(undoAvailable(bool)), this, SLOT(onUndoAvailable(bool)));
 	connect(&_inner, SIGNAL(redoAvailable(bool)), this, SLOT(onRedoAvailable(bool)));
 	if (App::wnd()) connect(&_inner, SIGNAL(selectionChanged()), App::wnd(), SLOT(updateGlobalMenu()));
-	
+
 	setCursor(style::cur_text);
 	if (!val.isEmpty()) {
 		_inner.setPlainText(val);
@@ -1412,7 +1412,7 @@ void InputField::paintEvent(QPaintEvent *e) {
 	if (_st.iconSprite.pxWidth()) {
 		p.drawSpriteLeft(_st.iconPosition, width(), _st.iconSprite);
 	}
-	
+
 	bool drawPlaceholder = _placeholderVisible;
 	if (_a_placeholderShift.animating()) {
 		p.setOpacity(a_placeholderOpacity.current());
@@ -1425,11 +1425,11 @@ void InputField::paintEvent(QPaintEvent *e) {
 		QRect r(rect().marginsRemoved(_st.textMargins + _st.placeholderMargins));
 		r.moveLeft(r.left() + a_placeholderLeft.current());
 		if (rtl()) r.moveLeft(width() - r.left() - r.width());
-	
+
 		p.setFont(_st.font);
 		p.setPen(a_placeholderFg.current());
 		p.drawText(r, _placeholder, _st.placeholderAlign);
-	
+
 		p.restore();
 	}
 	TWidget::paintEvent(e);
@@ -1663,7 +1663,7 @@ void InputField::processDocumentContentsChange(int position, int charsAdded) {
 					}
 
 					int32 emojiLen = 0;
-					emoji = emojiFromText(ch, e, emojiLen);
+					emoji = emojiFromText(ch, e, &emojiLen);
 					if (emoji) {
 						if (replacePosition >= 0) {
 							emoji = 0; // replace tilde char format first
@@ -2028,7 +2028,7 @@ MaskedInputField::MaskedInputField(QWidget *parent, const style::InputField &st,
 	setStyle(&_inputFieldStyle);
 	QLineEdit::setTextMargins(0, 0, 0, 0);
 	setContentsMargins(0, 0, 0, 0);
-	
+
 	setAttribute(Qt::WA_AcceptTouchEvents);
 	_touchTimer.setSingleShot(true);
 	connect(&_touchTimer, SIGNAL(timeout()), this, SLOT(onTouchTimer()));
diff --git a/Telegram/SourceFiles/gui/flattextarea.cpp b/Telegram/SourceFiles/gui/flattextarea.cpp
index 4f27183e7..3340bb1ab 100644
--- a/Telegram/SourceFiles/gui/flattextarea.cpp
+++ b/Telegram/SourceFiles/gui/flattextarea.cpp
@@ -738,7 +738,7 @@ void FlatTextarea::processDocumentContentsChange(int position, int charsAdded) {
 				const QChar *ch = t.constData(), *e = ch + t.size();
 				for (; ch != e; ++ch, ++fp) {
 					int32 emojiLen = 0;
-					emoji = emojiFromText(ch, e, emojiLen);
+					emoji = emojiFromText(ch, e, &emojiLen);
 					if (emoji) {
 						if (replacePosition >= 0) {
 							emoji = 0; // replace tilde char format first
diff --git a/Telegram/SourceFiles/gui/text.cpp b/Telegram/SourceFiles/gui/text.cpp
index 328762ab2..0dad2f9f4 100644
--- a/Telegram/SourceFiles/gui/text.cpp
+++ b/Telegram/SourceFiles/gui/text.cpp
@@ -598,7 +598,7 @@ public:
 
 	void parseEmojiFromCurrent() {
 		int len = 0;
-		EmojiPtr e = emojiFromText(ptr - emojiLookback, end, len);
+		EmojiPtr e = emojiFromText(ptr - emojiLookback, end, &len);
 		if (!e) return;
 
 		for (int l = len - emojiLookback - 1; l > 0; --l) {
@@ -4502,8 +4502,7 @@ goodCanBreakEntity = canBreakEntity;\
 #undef MARK_GOOD_AS_LEVEL
 
 		int elen = 0;
-		EmojiPtr e = emojiFromText(ch, end, elen);
-		if (e) {
+		if (EmojiPtr e = emojiFromText(ch, end, &elen)) {
 			for (int i = 0; i < elen; ++i, ++ch, ++s) {
 				if (ch->isHighSurrogate() && i + 1 < elen && (ch + 1)->isLowSurrogate()) {
 					++ch;
diff --git a/Telegram/SourceFiles/history.cpp b/Telegram/SourceFiles/history.cpp
index 7fc28f5f1..f76055744 100644
--- a/Telegram/SourceFiles/history.cpp
+++ b/Telegram/SourceFiles/history.cpp
@@ -4744,14 +4744,11 @@ bool HistoryGif::dataLoaded() const {
 HistorySticker::HistorySticker(DocumentData *document) : HistoryMedia()
 , _pixw(1)
 , _pixh(1)
-, _data(document) {
+, _data(document)
+, _emoji(_data->sticker()->alt) {
 	_data->thumb->load();
-	if (!_data->sticker()->alt.isEmpty()) {
-		_emoji = _data->sticker()->alt;
-		int32 elen = 0;
-		if (EmojiPtr e = emojiFromText(_emoji.constData(), _emoji.constEnd(), elen)) {
-			_emoji = emojiString(e);
-		}
+	if (EmojiPtr e = emojiFromText(_emoji)) {
+		_emoji = emojiString(e);
 	}
 }
 
diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp
index bc412b9d0..f44979901 100644
--- a/Telegram/SourceFiles/historywidget.cpp
+++ b/Telegram/SourceFiles/historywidget.cpp
@@ -2763,6 +2763,7 @@ HistoryWidget::HistoryWidget(QWidget *parent) : TWidget(parent)
 
 	_attachMention.hide();
 	connect(&_attachMention, SIGNAL(chosen(QString)), this, SLOT(onMentionHashtagOrBotCommandInsert(QString)));
+	connect(&_attachMention, SIGNAL(stickerSelected(DocumentData*)), this, SLOT(onStickerSend(DocumentData*)));
 	_field.installEventFilter(&_attachMention);
 	_field.setCtrlEnterSubmit(cCtrlEnter());
 
@@ -2807,7 +2808,7 @@ HistoryWidget::HistoryWidget(QWidget *parent) : TWidget(parent)
 }
 
 void HistoryWidget::start() {
-	connect(App::main(), SIGNAL(stickersUpdated()), &_emojiPan, SLOT(refreshStickers()));
+	connect(App::main(), SIGNAL(stickersUpdated()), this, SLOT(onStickersUpdated()));
 	connect(App::main(), SIGNAL(savedGifsUpdated()), &_emojiPan, SLOT(refreshSavedGifs()));
 
 	updateRecentStickers();
@@ -2816,6 +2817,11 @@ void HistoryWidget::start() {
 	connect(App::api(), SIGNAL(fullPeerUpdated(PeerData*)), this, SLOT(onFullPeerUpdated(PeerData*)));
 }
 
+void HistoryWidget::onStickersUpdated() {
+	_emojiPan.refreshStickers();
+	updateStickersByEmoji();
+}
+
 void HistoryWidget::onMentionHashtagOrBotCommandInsert(QString str) {
 	if (str.at(0) == '/') { // bot command
 		App::sendBotCommand(str);
@@ -2867,8 +2873,23 @@ void HistoryWidget::updateInlineBotQuery() {
 	}
 }
 
+void HistoryWidget::updateStickersByEmoji() {
+	int32 len = 0;
+	if (EmojiPtr emoji = emojiFromText(_field.getLastText(), &len)) {
+		if (_field.getLastText().size() <= len) {
+			_attachMention.showStickers(emoji);
+		} else {
+			len = 0;
+		}
+	}
+	if (!len) {
+		_attachMention.showStickers(EmojiPtr(0));
+	}
+}
+
 void HistoryWidget::onTextChange() {
 	updateInlineBotQuery();
+	updateStickersByEmoji();
 
 	if (_peer && (!_peer->isChannel() || _peer->isMegagroup() || !_peer->asChannel()->canPublish() || (!_peer->asChannel()->isBroadcast() && !_broadcast.checked()))) {
 		if (!_inlineBot && (_textUpdateEventsFlags & TextUpdateEventsSendTyping)) {
@@ -3180,6 +3201,7 @@ void HistoryWidget::stickersGot(const MTPmessages_AllStickers &stickers) {
 					++i;
 				}
 			}
+			Global::StickersByEmoji_RemovePack(it->stickers);
 			it = sets.erase(it);
 		}
 	}
@@ -5405,10 +5427,10 @@ void HistoryWidget::onFieldFocused() {
 }
 
 void HistoryWidget::onCheckMentionDropdown() {
-	if (!_history || _a_show.animating() || _inlineBot) return;
+	if (!_history || _a_show.animating()) return;
 
 	bool start = false;
-	QString query = _field.getMentionHashtagBotCommandPart(start);
+	QString query = _inlineBot ? QString() : _field.getMentionHashtagBotCommandPart(start);
 	if (!query.isEmpty()) {
 		if (query.at(0) == '#' && cRecentWriteHashtags().isEmpty() && cRecentSearchHashtags().isEmpty()) Local::readRecentHashtagsAndBots();
 		if (query.at(0) == '@' && cRecentInlineBots().isEmpty()) Local::readRecentHashtagsAndBots();
@@ -6529,6 +6551,13 @@ void HistoryWidget::sendExistingDocument(DocumentData *doc, const QString &capti
 
 	App::historyRegRandom(randomId, newId);
 
+	if (_attachMention.stickersShown()) {
+		setFieldText(QString());
+		_saveDraftText = true;
+		_saveDraftStart = getms();
+		onDraftSave();
+	}
+
 	if (!_attachMention.isHidden()) _attachMention.hideStart();
 	if (!_attachType.isHidden()) _attachType.hideStart();
 	if (!_emojiPan.isHidden()) _emojiPan.hideStart();
@@ -6813,7 +6842,9 @@ void HistoryWidget::updatePreview() {
 void HistoryWidget::onCancel() {
 	if (_inlineBot && _field.getLastText().startsWith('@' + _inlineBot->username + ' ')) {
 		setFieldText(QString(), TextUpdateEventsSaveDraft, false);
-	} else {
+	} else if (!_attachMention.isHidden()) {
+		_attachMention.hideStart();
+	} else  {
 		Ui::showChatsList();
 		emit cancelled();
 	}
diff --git a/Telegram/SourceFiles/historywidget.h b/Telegram/SourceFiles/historywidget.h
index 1936ccbb4..6aae8e548 100644
--- a/Telegram/SourceFiles/historywidget.h
+++ b/Telegram/SourceFiles/historywidget.h
@@ -452,6 +452,7 @@ public:
 
 	void updateFieldPlaceholder();
 	void updateInlineBotQuery();
+	void updateStickersByEmoji();
 
 	void uploadImage(const QImage &img, PrepareMediaType type, FileLoadForceConfirmType confirm = FileLoadNoForceConfirm, const QString &source = QString(), bool withText = false);
 	void uploadFile(const QString &file, PrepareMediaType type, FileLoadForceConfirmType confirm = FileLoadNoForceConfirm, bool withText = false); // with confirmation
@@ -643,6 +644,7 @@ public slots:
 	void onCmdStart();
 
 	void activate();
+	void onStickersUpdated();
 	void onMentionHashtagOrBotCommandInsert(QString str);
 	void onTextChange();
 
diff --git a/Telegram/SourceFiles/localstorage.cpp b/Telegram/SourceFiles/localstorage.cpp
index 2e34afc7f..c527f8781 100644
--- a/Telegram/SourceFiles/localstorage.cpp
+++ b/Telegram/SourceFiles/localstorage.cpp
@@ -2920,6 +2920,8 @@ namespace Local {
 		RecentStickerPack &recent(cRefRecentStickers());
 		recent.clear();
 
+		Global::SetStickersByEmoji(StickersByEmojiMap());
+
 		StickerSet &def(sets.insert(DefaultStickerSetId, StickerSet(DefaultStickerSetId, 0, lang(lng_stickers_default_set), QString(), 0, 0, MTPDstickerSet::flag_official)).value());
 		StickerSet &custom(sets.insert(CustomStickerSetId, StickerSet(CustomStickerSetId, 0, lang(lng_custom_stickers), QString(), 0, 0, 0)).value());
 
@@ -2993,6 +2995,8 @@ namespace Local {
 		StickerSetsOrder &order(cRefStickerSetsOrder());
 		order.clear();
 
+		Global::SetStickersByEmoji(StickersByEmojiMap());
+
 		quint32 cnt;
 		QByteArray hash;
 		stickers.stream >> cnt >> hash; // ignore hash, it is counted
@@ -3073,6 +3077,10 @@ namespace Local {
 				set.stickers.push_back(doc);
 				++set.count;
 			}
+
+			if (setId != CustomStickerSetId) {
+				Global::StickersByEmoji_AddPack(set.stickers);
+			}
 		}
 	}
 
diff --git a/Telegram/SourceFiles/logs.h b/Telegram/SourceFiles/logs.h
index d1bd7199e..bb54a12d5 100644
--- a/Telegram/SourceFiles/logs.h
+++ b/Telegram/SourceFiles/logs.h
@@ -84,11 +84,13 @@ void logWrite(const QString &v);
 
 static volatile int *t_assert_nullptr = 0;
 inline void t_noop() {}
-inline void t_assert_fail(const char *condition, const char *file, int32 line) {
-	LOG(("Assertion Failed! \"%1\" %2:%3").arg(condition).arg(file).arg(line));
+inline void t_assert_fail(const char *message, const char *file, int32 line) {
+	LOG(("Assertion Failed! %1 %2:%3").arg(message).arg(file).arg(line));
 	*t_assert_nullptr = 0;
 }
-#define t_assert(cond) ((!(cond)) ? t_assert_fail(#cond, __FILE__, __LINE__) : t_noop())
+#define t_assert_full(condition, message, file, line) ((!(condition)) ? t_assert_fail(message, file, line) : t_noop())
+#define t_assert_c(condition, comment) t_assert_full(condition, "\"" #condition "\" (" comment ")", __FILE__, __LINE__)
+#define t_assert(condition) t_assert_full(condition, "\"" #condition "\"", __FILE__, __LINE__)
 
 void logsInit();
 void logsInitDebug();
diff --git a/Telegram/SourceFiles/main.cpp b/Telegram/SourceFiles/main.cpp
index deb1e3918..a64c8641d 100644
--- a/Telegram/SourceFiles/main.cpp
+++ b/Telegram/SourceFiles/main.cpp
@@ -32,8 +32,6 @@ int main(int argc, char *argv[]) {
     //signal(SIGSEGV, _sigsegvHandler);
 #endif
 
-	LibrariesInitializer _init;
-
 	settingsParseArgs(argc, argv);
 	for (int32 i = 0; i < argc; ++i) {
 		if (string("-fixprevious") == argv[i]) {
@@ -44,6 +42,8 @@ int main(int argc, char *argv[]) {
 	}
 	logsInit();
 
+	Global::Initializer _init;
+
 	Local::readSettings();
 	if (Local::oldSettingsVersion() < AppVersion) {
 		psNewVersion();
diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp
index 5ba5539b8..c55324669 100644
--- a/Telegram/SourceFiles/mainwidget.cpp
+++ b/Telegram/SourceFiles/mainwidget.cpp
@@ -4607,8 +4607,14 @@ void MainWidget::feedUpdate(const MTPUpdate &update) {
 				const MTPDstickerSet &s(set.vset.c_stickerSet());
 
 				StickerSets &sets(cRefStickerSets());
-
-				sets.insert(s.vid.v, StickerSet(s.vid.v, s.vaccess_hash.v, stickerSetTitle(s), qs(s.vshort_name), s.vcount.v, s.vhash.v, s.vflags.v)).value().stickers = pack;
+				StickerSets::iterator it = sets.find(s.vid.v);
+				if (it != sets.cend()) {
+					Global::StickersByEmoji_RemovePack(it->stickers);
+				} else {
+					it = sets.insert(s.vid.v, StickerSet(s.vid.v, s.vaccess_hash.v, stickerSetTitle(s), qs(s.vshort_name), s.vcount.v, s.vhash.v, s.vflags.v));
+				}
+				it->stickers = pack;
+				Global::StickersByEmoji_AddPack(pack);
 
 				StickerSetsOrder &order(cRefStickerSetsOrder());
 				int32 insertAtIndex = 0, currentIndex = order.indexOf(s.vid.v);
diff --git a/Telegram/SourceFiles/settings.cpp b/Telegram/SourceFiles/settings.cpp
index 8b2700c4e..b5d400977 100644
--- a/Telegram/SourceFiles/settings.cpp
+++ b/Telegram/SourceFiles/settings.cpp
@@ -190,7 +190,6 @@ void settingsParseArgs(int argc, char *argv[]) {
 		gCustomNotifies = false;
 	}
 #endif
-    memset_rand(&gInstance, sizeof(gInstance));
 	gExeDir = psCurrentExeDirectory(argc, argv);
 	gExeName = psCurrentExeName(argc, argv);
     for (int32 i = 0; i < argc; ++i) {
diff --git a/Telegram/SourceFiles/structs.cpp b/Telegram/SourceFiles/structs.cpp
index 34bd47fba..de8acf32e 100644
--- a/Telegram/SourceFiles/structs.cpp
+++ b/Telegram/SourceFiles/structs.cpp
@@ -1513,6 +1513,8 @@ void DocumentData::setattributes(const QVector<MTPDocumentAttribute> &attributes
 			_additional = 0;
 		} break;
 		case mtpc_documentAttributeSticker: {
+			bool wasByEmoji = Global::StickersByEmoji_Remove(this);
+
 			const MTPDdocumentAttributeSticker &d(attributes[i].c_documentAttributeSticker());
 			if (type == FileDocument) {
 				type = StickerDocument;
@@ -1522,6 +1524,7 @@ void DocumentData::setattributes(const QVector<MTPDocumentAttribute> &attributes
 			if (sticker()) {
 				sticker()->alt = qs(d.valt);
 				sticker()->set = d.vstickerset;
+				if (wasByEmoji) Global::StickersByEmoji_Add(this);
 			}
 		} break;
 		case mtpc_documentAttributeVideo: {
diff --git a/Telegram/SourceFiles/types.cpp b/Telegram/SourceFiles/types.cpp
index 6d9e7e275..0b3406426 100644
--- a/Telegram/SourceFiles/types.cpp
+++ b/Telegram/SourceFiles/types.cpp
@@ -264,7 +264,7 @@ namespace {
 	_MsStarter _msStarter;
 }
 
-LibrariesInitializer::LibrariesInitializer() {
+void initThirdParty() {
 	if (!RAND_status()) { // should be always inited in all modern OS
 		char buf[16];
 		memcpy(buf, &_msStart, 8);
@@ -296,7 +296,7 @@ LibrariesInitializer::LibrariesInitializer() {
 	_sslInited = true;
 }
 
-LibrariesInitializer::~LibrariesInitializer() {
+void deinitThirdParty() {
 	av_lockmgr_register(0);
 
 	delete[] _sslLocks;
diff --git a/Telegram/SourceFiles/types.h b/Telegram/SourceFiles/types.h
index 0a6359661..264e4cfc1 100644
--- a/Telegram/SourceFiles/types.h
+++ b/Telegram/SourceFiles/types.h
@@ -133,11 +133,8 @@ inline void mylocaltime(struct tm * _Tm, const time_t * _Time) {
 #endif
 }
 
-class LibrariesInitializer {
-public:
-	LibrariesInitializer();
-	~LibrariesInitializer();
-};
+void initThirdParty(); // called by Global::Initializer
+void deinitThirdParty();
 
 bool checkms(); // returns true if time has changed
 uint64 getms(bool checked = false);
@@ -458,6 +455,9 @@ MimeType mimeTypeForName(const QString &mime);
 MimeType mimeTypeForFile(const QFileInfo &file);
 MimeType mimeTypeForData(const QByteArray &data);
 
+inline int32 rowscount(int32 count, int32 perrow) {
+	return (count + perrow - 1) / perrow;
+}
 inline int32 floorclamp(int32 value, int32 step, int32 lowest, int32 highest) {
 	return qMin(qMax(value / step, lowest), highest);
 }