8884 lines
		
	
	
	
		
			295 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			8884 lines
		
	
	
	
		
			295 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
/*
 | 
						|
This file is part of Telegram Desktop,
 | 
						|
the official desktop version of Telegram messaging app, see https://telegram.org
 | 
						|
 | 
						|
Telegram Desktop is free software: you can redistribute it and/or modify
 | 
						|
it under the terms of the GNU General Public License as published by
 | 
						|
the Free Software Foundation, either version 3 of the License, or
 | 
						|
(at your option) any later version.
 | 
						|
 | 
						|
It is distributed in the hope that it will be useful,
 | 
						|
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
						|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 | 
						|
GNU General Public License for more details.
 | 
						|
 | 
						|
In addition, as a special exception, the copyright holders give permission
 | 
						|
to link the code of portions of this program with the OpenSSL library.
 | 
						|
 | 
						|
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
 | 
						|
Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
 | 
						|
*/
 | 
						|
#include "stdafx.h"
 | 
						|
#include "historywidget.h"
 | 
						|
 | 
						|
#include "styles/style_history.h"
 | 
						|
#include "styles/style_dialogs.h"
 | 
						|
#include "boxes/confirmbox.h"
 | 
						|
#include "boxes/photosendbox.h"
 | 
						|
#include "boxes/sharebox.h"
 | 
						|
#include "ui/filedialog.h"
 | 
						|
#include "ui/toast/toast.h"
 | 
						|
#include "ui/buttons/history_down_button.h"
 | 
						|
#include "ui/inner_dropdown.h"
 | 
						|
#include "inline_bots/inline_bot_result.h"
 | 
						|
#include "data/data_drafts.h"
 | 
						|
#include "history/history_service_layout.h"
 | 
						|
#include "history/history_media_types.h"
 | 
						|
#include "profile/profile_members_widget.h"
 | 
						|
#include "core/click_handler_types.h"
 | 
						|
#include "stickers/emoji_pan.h"
 | 
						|
#include "lang.h"
 | 
						|
#include "application.h"
 | 
						|
#include "dropdown.h"
 | 
						|
#include "mainwidget.h"
 | 
						|
#include "mainwindow.h"
 | 
						|
#include "passcodewidget.h"
 | 
						|
#include "mainwindow.h"
 | 
						|
#include "fileuploader.h"
 | 
						|
#include "media/media_audio.h"
 | 
						|
#include "localstorage.h"
 | 
						|
#include "apiwrap.h"
 | 
						|
#include "window/top_bar_widget.h"
 | 
						|
#include "window/chat_background.h"
 | 
						|
#include "observer_peer.h"
 | 
						|
#include "core/qthelp_regex.h"
 | 
						|
 | 
						|
namespace {
 | 
						|
 | 
						|
QString mimeTagFromTag(const QString &tagId) {
 | 
						|
	if (tagId.startsWith(qstr("mention://"))) {
 | 
						|
		return tagId + ':' + QString::number(MTP::authedId());
 | 
						|
	}
 | 
						|
	return tagId;
 | 
						|
}
 | 
						|
 | 
						|
QMimeData *mimeDataFromTextWithEntities(const TextWithEntities &forClipboard) {
 | 
						|
	if (forClipboard.text.isEmpty()) {
 | 
						|
		return nullptr;
 | 
						|
	}
 | 
						|
 | 
						|
	auto result = new QMimeData();
 | 
						|
	result->setText(forClipboard.text);
 | 
						|
	auto tags = textTagsFromEntities(forClipboard.entities);
 | 
						|
	if (!tags.isEmpty()) {
 | 
						|
		for (auto &tag : tags) {
 | 
						|
			tag.id = mimeTagFromTag(tag.id);
 | 
						|
		}
 | 
						|
		result->setData(FlatTextarea::tagsMimeType(), FlatTextarea::serializeTagsList(tags));
 | 
						|
	}
 | 
						|
	return result;
 | 
						|
}
 | 
						|
 | 
						|
// For mention tags save and validate userId, ignore tags for different userId.
 | 
						|
class FieldTagMimeProcessor : public FlatTextarea::TagMimeProcessor {
 | 
						|
public:
 | 
						|
	QString mimeTagFromTag(const QString &tagId) override {
 | 
						|
		return ::mimeTagFromTag(tagId);
 | 
						|
	}
 | 
						|
 | 
						|
	QString tagFromMimeTag(const QString &mimeTag) override {
 | 
						|
		if (mimeTag.startsWith(qstr("mention://"))) {
 | 
						|
			auto match = QRegularExpression(":(\\d+)$").match(mimeTag);
 | 
						|
			if (!match.hasMatch() || match.capturedRef(1).toInt() != MTP::authedId()) {
 | 
						|
				return QString();
 | 
						|
			}
 | 
						|
			return mimeTag.mid(0, mimeTag.size() - match.capturedLength());
 | 
						|
		}
 | 
						|
		return mimeTag;
 | 
						|
	}
 | 
						|
 | 
						|
};
 | 
						|
 | 
						|
constexpr int ScrollDateHideTimeout = 1000;
 | 
						|
 | 
						|
ApiWrap::RequestMessageDataCallback replyEditMessageDataCallback() {
 | 
						|
	return [](ChannelData *channel, MsgId msgId) {
 | 
						|
		if (App::main()) {
 | 
						|
			App::main()->messageDataReceived(channel, msgId);
 | 
						|
		}
 | 
						|
	};
 | 
						|
}
 | 
						|
 | 
						|
} // namespace
 | 
						|
 | 
						|
// flick scroll taken from http://qt-project.org/doc/qt-4.8/demos-embedded-anomaly-src-flickcharm-cpp.html
 | 
						|
 | 
						|
HistoryInner::HistoryInner(HistoryWidget *historyWidget, ScrollArea *scroll, History *history) : TWidget(nullptr)
 | 
						|
, _peer(history->peer)
 | 
						|
, _migrated(history->peer->migrateFrom() ? App::history(history->peer->migrateFrom()->id) : nullptr)
 | 
						|
, _history(history)
 | 
						|
, _widget(historyWidget)
 | 
						|
, _scroll(scroll) {
 | 
						|
	_touchSelectTimer.setSingleShot(true);
 | 
						|
	connect(&_touchSelectTimer, SIGNAL(timeout()), this, SLOT(onTouchSelect()));
 | 
						|
 | 
						|
	setAttribute(Qt::WA_AcceptTouchEvents);
 | 
						|
	connect(&_touchScrollTimer, SIGNAL(timeout()), this, SLOT(onTouchScrollTimer()));
 | 
						|
 | 
						|
	_trippleClickTimer.setSingleShot(true);
 | 
						|
 | 
						|
	connect(&_scrollDateHideTimer, SIGNAL(timeout()), this, SLOT(onScrollDateHide()));
 | 
						|
 | 
						|
	notifyIsBotChanged();
 | 
						|
 | 
						|
	setMouseTracking(true);
 | 
						|
	subscribe(Global::RefItemRemoved(), [this](HistoryItem *item) {
 | 
						|
		itemRemoved(item);
 | 
						|
	});
 | 
						|
}
 | 
						|
 | 
						|
void HistoryInner::messagesReceived(PeerData *peer, const QVector<MTPMessage> &messages) {
 | 
						|
	if (_history && _history->peer == peer) {
 | 
						|
		_history->addOlderSlice(messages);
 | 
						|
	} else if (_migrated && _migrated->peer == peer) {
 | 
						|
		bool newLoaded = (_migrated && _migrated->isEmpty() && !_history->isEmpty());
 | 
						|
		_migrated->addOlderSlice(messages);
 | 
						|
		if (newLoaded) {
 | 
						|
			_migrated->addNewerSlice(QVector<MTPMessage>());
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryInner::messagesReceivedDown(PeerData *peer, const QVector<MTPMessage> &messages) {
 | 
						|
	if (_history && _history->peer == peer) {
 | 
						|
		bool oldLoaded = (_migrated && _history->isEmpty() && !_migrated->isEmpty());
 | 
						|
		_history->addNewerSlice(messages);
 | 
						|
		if (oldLoaded) {
 | 
						|
			_history->addOlderSlice(QVector<MTPMessage>());
 | 
						|
		}
 | 
						|
	} else if (_migrated && _migrated->peer == peer) {
 | 
						|
		_migrated->addNewerSlice(messages);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryInner::repaintItem(const HistoryItem *item) {
 | 
						|
	if (!item || item->detached() || !_history) return;
 | 
						|
	int32 msgy = itemTop(item);
 | 
						|
	if (msgy >= 0) {
 | 
						|
		update(0, msgy, width(), item->height());
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
namespace {
 | 
						|
	// helper binary search for an item in a list that is not completely below the given bottom of the visible area
 | 
						|
	// is applied once for blocks list in a history and once for items list in the found block
 | 
						|
	template <typename T>
 | 
						|
	int binarySearchBlocksOrItems(const T &list, int bottom) {
 | 
						|
		int start = 0, end = list.size();
 | 
						|
		while (end - start > 1) {
 | 
						|
			int middle = (start + end) / 2;
 | 
						|
			if (list.at(middle)->y >= bottom) {
 | 
						|
				end = middle;
 | 
						|
			} else {
 | 
						|
				start = middle;
 | 
						|
			}
 | 
						|
		}
 | 
						|
		return start;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
template <typename Method>
 | 
						|
void HistoryInner::enumerateItemsInHistory(History *history, int historytop, Method method) {
 | 
						|
	// no displayed messages in this history
 | 
						|
	if (historytop < 0 || history->isEmpty()) {
 | 
						|
		return;
 | 
						|
	}
 | 
						|
	if (_visibleAreaBottom <= historytop || historytop + history->height <= _visibleAreaTop) {
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	// binary search for blockIndex of the first block that is not completely below the visible area
 | 
						|
	int blockIndex = binarySearchBlocksOrItems(history->blocks, _visibleAreaBottom - historytop);
 | 
						|
 | 
						|
	// binary search for itemIndex of the first item that is not completely below the visible area
 | 
						|
	HistoryBlock *block = history->blocks.at(blockIndex);
 | 
						|
	int blocktop = historytop + block->y;
 | 
						|
	int itemIndex = binarySearchBlocksOrItems(block->items, _visibleAreaBottom - blocktop);
 | 
						|
 | 
						|
	while (true) {
 | 
						|
		while (itemIndex >= 0) {
 | 
						|
			HistoryItem *item = block->items.at(itemIndex--);
 | 
						|
			int itemtop = blocktop + item->y;
 | 
						|
			int itembottom = itemtop + item->height();
 | 
						|
 | 
						|
			// binary search should've skipped all the items that are below the visible area
 | 
						|
			t_assert(itemtop < _visibleAreaBottom);
 | 
						|
 | 
						|
			if (!method(item, itemtop, itembottom)) {
 | 
						|
				return;
 | 
						|
			}
 | 
						|
 | 
						|
			// skip all the items that are above the visible area
 | 
						|
			if (itemtop <= _visibleAreaTop) {
 | 
						|
				return;
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		// skip all the rest blocks that are above the visible area
 | 
						|
		if (blocktop <= _visibleAreaTop) {
 | 
						|
			return;
 | 
						|
		}
 | 
						|
 | 
						|
		if (--blockIndex < 0) {
 | 
						|
			return;
 | 
						|
		} else {
 | 
						|
			block = history->blocks.at(blockIndex);
 | 
						|
			blocktop = historytop + block->y;
 | 
						|
			itemIndex = block->items.size() - 1;
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
template <typename Method>
 | 
						|
void HistoryInner::enumerateUserpics(Method method) {
 | 
						|
	if ((!_history || !_history->canHaveFromPhotos()) && (!_migrated || !_migrated->canHaveFromPhotos())) {
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	// find and remember the bottom of an attached messages pack
 | 
						|
	// -1 means we didn't find an attached to previous message yet
 | 
						|
	int lowestAttachedItemBottom = -1;
 | 
						|
 | 
						|
	auto userpicCallback = [this, &lowestAttachedItemBottom, &method](HistoryItem *item, int itemtop, int itembottom) {
 | 
						|
		// skip all service messages
 | 
						|
		auto message = item->toHistoryMessage();
 | 
						|
		if (!message) return true;
 | 
						|
 | 
						|
		if (lowestAttachedItemBottom < 0 && message->isAttachedToPrevious()) {
 | 
						|
			lowestAttachedItemBottom = itembottom - message->marginBottom();
 | 
						|
		}
 | 
						|
 | 
						|
		// call method on a userpic for all messages that have it and for those who are not showing it
 | 
						|
		// because of their attachment to the previous message if they are top-most visible
 | 
						|
		if (message->displayFromPhoto() || (message->hasFromPhoto() && itemtop <= _visibleAreaTop)) {
 | 
						|
			if (lowestAttachedItemBottom < 0) {
 | 
						|
				lowestAttachedItemBottom = itembottom - message->marginBottom();
 | 
						|
			}
 | 
						|
			// attach userpic to the top of the visible area with the same margin as it is from the left side
 | 
						|
			int userpicTop = qMax(itemtop + message->marginTop(), _visibleAreaTop + st::msgMargin.left());
 | 
						|
 | 
						|
			// do not let the userpic go below the attached messages pack bottom line
 | 
						|
			userpicTop = qMin(userpicTop, lowestAttachedItemBottom - st::msgPhotoSize);
 | 
						|
 | 
						|
			// call the template callback function that was passed
 | 
						|
			// and return if it finished everything it needed
 | 
						|
			if (!method(message, userpicTop)) {
 | 
						|
				return false;
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		// forget the found bottom of the pack, search for the next one from scratch
 | 
						|
		if (!message->isAttachedToPrevious()) {
 | 
						|
			lowestAttachedItemBottom = -1;
 | 
						|
		}
 | 
						|
 | 
						|
		return true;
 | 
						|
	};
 | 
						|
 | 
						|
	enumerateItems(userpicCallback);
 | 
						|
}
 | 
						|
 | 
						|
template <typename Method>
 | 
						|
void HistoryInner::enumerateDates(Method method) {
 | 
						|
	int drawtop = historyDrawTop();
 | 
						|
 | 
						|
	// find and remember the bottom of an single-day messages pack
 | 
						|
	// -1 means we didn't find a same-day with previous message yet
 | 
						|
	int lowestInOneDayItemBottom = -1;
 | 
						|
 | 
						|
	auto dateCallback = [this, &lowestInOneDayItemBottom, &method, drawtop](HistoryItem *item, int itemtop, int itembottom) {
 | 
						|
		if (lowestInOneDayItemBottom < 0 && item->isInOneDayWithPrevious()) {
 | 
						|
			lowestInOneDayItemBottom = itembottom - item->marginBottom();
 | 
						|
		}
 | 
						|
 | 
						|
		// call method on a date for all messages that have it and for those who are not showing it
 | 
						|
		// because they are in a one day together with the previous message if they are top-most visible
 | 
						|
		if (item->displayDate() || (!item->isEmpty() && itemtop <= _visibleAreaTop)) {
 | 
						|
			// skip the date of history migrate item if it will be in migrated
 | 
						|
			if (itemtop < drawtop && item->history() == _history) {
 | 
						|
				if (itemtop > _visibleAreaTop) {
 | 
						|
					// previous item (from the _migrated history) is drawing date now
 | 
						|
					return false;
 | 
						|
				} else if (item == _history->blocks.front()->items.front() && item->isGroupMigrate()
 | 
						|
					&& _migrated->blocks.back()->items.back()->isGroupMigrate()) {
 | 
						|
					// this item is completely invisible and should be completely ignored
 | 
						|
					return false;
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
			if (lowestInOneDayItemBottom < 0) {
 | 
						|
				lowestInOneDayItemBottom = itembottom - item->marginBottom();
 | 
						|
			}
 | 
						|
			// attach date to the top of the visible area with the same margin as it has in service message
 | 
						|
			int dateTop = qMax(itemtop, _visibleAreaTop) + st::msgServiceMargin.top();
 | 
						|
 | 
						|
			// do not let the date go below the single-day messages pack bottom line
 | 
						|
			int dateHeight = st::msgServicePadding.bottom() + st::msgServiceFont->height + st::msgServicePadding.top();
 | 
						|
			dateTop = qMin(dateTop, lowestInOneDayItemBottom - dateHeight);
 | 
						|
 | 
						|
			// call the template callback function that was passed
 | 
						|
			// and return if it finished everything it needed
 | 
						|
			if (!method(item, itemtop, dateTop)) {
 | 
						|
				return false;
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		// forget the found bottom of the pack, search for the next one from scratch
 | 
						|
		if (!item->isInOneDayWithPrevious()) {
 | 
						|
			lowestInOneDayItemBottom = -1;
 | 
						|
		}
 | 
						|
 | 
						|
		return true;
 | 
						|
	};
 | 
						|
 | 
						|
	enumerateItems(dateCallback);
 | 
						|
}
 | 
						|
 | 
						|
void HistoryInner::paintEvent(QPaintEvent *e) {
 | 
						|
	if (!App::main() || (App::wnd() && App::wnd()->contentOverlapped(this, e))) {
 | 
						|
		return;
 | 
						|
	}
 | 
						|
	if (hasPendingResizedItems()) {
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	Painter p(this);
 | 
						|
	QRect r(e->rect());
 | 
						|
	bool trivial = (rect() == r);
 | 
						|
	if (!trivial) {
 | 
						|
		p.setClipRect(r);
 | 
						|
	}
 | 
						|
	uint64 ms = getms();
 | 
						|
 | 
						|
	bool historyDisplayedEmpty = (_history->isDisplayedEmpty() && (!_migrated || _migrated->isDisplayedEmpty()));
 | 
						|
	bool noHistoryDisplayed = _firstLoading || historyDisplayedEmpty;
 | 
						|
	if (!_firstLoading && _botAbout && !_botAbout->info->text.isEmpty() && _botAbout->height > 0) {
 | 
						|
		if (r.y() < _botAbout->rect.y() + _botAbout->rect.height() && r.y() + r.height() > _botAbout->rect.y()) {
 | 
						|
			textstyleSet(&st::inTextStyle);
 | 
						|
			App::roundRect(p, _botAbout->rect, st::msgInBg, MessageInCorners, &st::msgInShadow);
 | 
						|
 | 
						|
			p.setFont(st::msgNameFont);
 | 
						|
			p.setPen(st::black);
 | 
						|
			p.drawText(_botAbout->rect.left() + st::msgPadding.left(), _botAbout->rect.top() + st::msgPadding.top() + st::msgNameFont->ascent, lang(lng_bot_description));
 | 
						|
 | 
						|
			_botAbout->info->text.draw(p, _botAbout->rect.left() + st::msgPadding.left(), _botAbout->rect.top() + st::msgPadding.top() + st::msgNameFont->height + st::botDescSkip, _botAbout->width);
 | 
						|
 | 
						|
			textstyleRestore();
 | 
						|
		}
 | 
						|
	} else if (noHistoryDisplayed) {
 | 
						|
		HistoryLayout::paintEmpty(p, width(), height());
 | 
						|
	}
 | 
						|
	if (!noHistoryDisplayed) {
 | 
						|
		adjustCurrent(r.top());
 | 
						|
 | 
						|
		SelectedItems::const_iterator selEnd = _selected.cend();
 | 
						|
		bool hasSel = !_selected.isEmpty();
 | 
						|
 | 
						|
		int32 drawToY = r.y() + r.height();
 | 
						|
 | 
						|
		int32 selfromy = itemTop(_dragSelFrom), seltoy = itemTop(_dragSelTo);
 | 
						|
		if (selfromy < 0 || seltoy < 0) {
 | 
						|
			selfromy = seltoy = -1;
 | 
						|
		} else {
 | 
						|
			seltoy += _dragSelTo->height();
 | 
						|
		}
 | 
						|
 | 
						|
		int32 mtop = migratedTop(), htop = historyTop(), hdrawtop = historyDrawTop();
 | 
						|
		if (mtop >= 0) {
 | 
						|
			int32 iBlock = (_curHistory == _migrated ? _curBlock : (_migrated->blocks.size() - 1));
 | 
						|
			HistoryBlock *block = _migrated->blocks[iBlock];
 | 
						|
			int32 iItem = (_curHistory == _migrated ? _curItem : (block->items.size() - 1));
 | 
						|
			HistoryItem *item = block->items[iItem];
 | 
						|
 | 
						|
			int32 y = mtop + block->y + item->y;
 | 
						|
			p.save();
 | 
						|
			p.translate(0, y);
 | 
						|
			if (r.y() < y + item->height()) while (y < drawToY) {
 | 
						|
				TextSelection sel;
 | 
						|
				if (y >= selfromy && y < seltoy) {
 | 
						|
					if (_dragSelecting && !item->serviceMsg() && item->id > 0) {
 | 
						|
						sel = FullSelection;
 | 
						|
					}
 | 
						|
				} else if (hasSel) {
 | 
						|
					auto i = _selected.constFind(item);
 | 
						|
					if (i != selEnd) {
 | 
						|
						sel = i.value();
 | 
						|
					}
 | 
						|
				}
 | 
						|
				item->draw(p, r.translated(0, -y), sel, ms);
 | 
						|
 | 
						|
				if (item->hasViews()) {
 | 
						|
					App::main()->scheduleViewIncrement(item);
 | 
						|
				}
 | 
						|
 | 
						|
				int32 h = item->height();
 | 
						|
				p.translate(0, h);
 | 
						|
				y += h;
 | 
						|
 | 
						|
				++iItem;
 | 
						|
				if (iItem == block->items.size()) {
 | 
						|
					iItem = 0;
 | 
						|
					++iBlock;
 | 
						|
					if (iBlock == _migrated->blocks.size()) {
 | 
						|
						break;
 | 
						|
					}
 | 
						|
					block = _migrated->blocks[iBlock];
 | 
						|
				}
 | 
						|
				item = block->items[iItem];
 | 
						|
			}
 | 
						|
			p.restore();
 | 
						|
		}
 | 
						|
		if (htop >= 0) {
 | 
						|
			int32 iBlock = (_curHistory == _history ? _curBlock : 0);
 | 
						|
			HistoryBlock *block = _history->blocks[iBlock];
 | 
						|
			int32 iItem = (_curHistory == _history ? _curItem : 0);
 | 
						|
			HistoryItem *item = block->items[iItem];
 | 
						|
 | 
						|
			QRect historyRect = r.intersected(QRect(0, hdrawtop, width(), r.top() + r.height()));
 | 
						|
			int32 y = htop + block->y + item->y;
 | 
						|
			p.save();
 | 
						|
			p.translate(0, y);
 | 
						|
			while (y < drawToY) {
 | 
						|
				int32 h = item->height();
 | 
						|
				if (historyRect.y() < y + h && hdrawtop < y + h) {
 | 
						|
					TextSelection sel;
 | 
						|
					if (y >= selfromy && y < seltoy) {
 | 
						|
						if (_dragSelecting && !item->serviceMsg() && item->id > 0) {
 | 
						|
							sel = FullSelection;
 | 
						|
						}
 | 
						|
					} else if (hasSel) {
 | 
						|
						auto i = _selected.constFind(item);
 | 
						|
						if (i != selEnd) {
 | 
						|
							sel = i.value();
 | 
						|
						}
 | 
						|
					}
 | 
						|
					item->draw(p, historyRect.translated(0, -y), sel, ms);
 | 
						|
 | 
						|
					if (item->hasViews()) {
 | 
						|
						App::main()->scheduleViewIncrement(item);
 | 
						|
					}
 | 
						|
				}
 | 
						|
				p.translate(0, h);
 | 
						|
				y += h;
 | 
						|
 | 
						|
				++iItem;
 | 
						|
				if (iItem == block->items.size()) {
 | 
						|
					iItem = 0;
 | 
						|
					++iBlock;
 | 
						|
					if (iBlock == _history->blocks.size()) {
 | 
						|
						break;
 | 
						|
					}
 | 
						|
					block = _history->blocks[iBlock];
 | 
						|
				}
 | 
						|
				item = block->items[iItem];
 | 
						|
			}
 | 
						|
			p.restore();
 | 
						|
		}
 | 
						|
 | 
						|
		if (mtop >= 0 || htop >= 0) {
 | 
						|
			enumerateUserpics([&p, &r](HistoryMessage *message, int userpicTop) {
 | 
						|
				// stop the enumeration if the userpic is above the painted rect
 | 
						|
				if (userpicTop + st::msgPhotoSize <= r.top()) {
 | 
						|
					return false;
 | 
						|
				}
 | 
						|
 | 
						|
				// paint the userpic if it intersects the painted rect
 | 
						|
				if (userpicTop < r.top() + r.height()) {
 | 
						|
					message->from()->paintUserpicLeft(p, st::msgPhotoSize, st::msgMargin.left(), userpicTop, message->history()->width);
 | 
						|
				}
 | 
						|
				return true;
 | 
						|
			});
 | 
						|
 | 
						|
			int dateHeight = st::msgServicePadding.bottom() + st::msgServiceFont->height + st::msgServicePadding.top();
 | 
						|
			//QDate lastDate;
 | 
						|
			//if (!_history->isEmpty()) {
 | 
						|
			//	lastDate = _history->blocks.back()->items.back()->date.date();
 | 
						|
			//}
 | 
						|
 | 
						|
			//// if item top is before this value always show date as a floating date
 | 
						|
			//int showFloatingBefore = height() - 2 * (_visibleAreaBottom - _visibleAreaTop) - dateHeight;
 | 
						|
 | 
						|
			auto scrollDateOpacity = _scrollDateOpacity.current(ms, _scrollDateShown ? 1. : 0.);
 | 
						|
			enumerateDates([&p, &r, scrollDateOpacity, dateHeight/*, lastDate, showFloatingBefore*/](HistoryItem *item, int itemtop, int dateTop) {
 | 
						|
				// stop the enumeration if the date is above the painted rect
 | 
						|
				if (dateTop + dateHeight <= r.top()) {
 | 
						|
					return false;
 | 
						|
				}
 | 
						|
 | 
						|
				bool displayDate = item->displayDate();
 | 
						|
				bool dateInPlace = displayDate;
 | 
						|
				if (dateInPlace) {
 | 
						|
					int correctDateTop = itemtop + st::msgServiceMargin.top();
 | 
						|
					dateInPlace = (dateTop < correctDateTop + dateHeight);
 | 
						|
				}
 | 
						|
				//bool noFloatingDate = (item->date.date() == lastDate && displayDate);
 | 
						|
				//if (noFloatingDate) {
 | 
						|
				//	if (itemtop < showFloatingBefore) {
 | 
						|
				//		noFloatingDate = false;
 | 
						|
				//	}
 | 
						|
				//}
 | 
						|
 | 
						|
				// paint the date if it intersects the painted rect
 | 
						|
				if (dateTop < r.top() + r.height()) {
 | 
						|
					auto opacity = (dateInPlace/* || noFloatingDate*/) ? 1. : scrollDateOpacity;
 | 
						|
					if (opacity > 0.) {
 | 
						|
						p.setOpacity(opacity);
 | 
						|
						int dateY = /*noFloatingDate ? itemtop :*/ (dateTop - st::msgServiceMargin.top());
 | 
						|
						int width = item->history()->width;
 | 
						|
						if (auto date = item->Get<HistoryMessageDate>()) {
 | 
						|
							date->paint(p, dateY, width);
 | 
						|
						} else {
 | 
						|
							HistoryLayout::ServiceMessagePainter::paintDate(p, item->date, dateY, width);
 | 
						|
						}
 | 
						|
					}
 | 
						|
				}
 | 
						|
				return true;
 | 
						|
			});
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
bool HistoryInner::event(QEvent *e) {
 | 
						|
	if (e->type() == QEvent::TouchBegin || e->type() == QEvent::TouchUpdate || e->type() == QEvent::TouchEnd || e->type() == QEvent::TouchCancel) {
 | 
						|
		QTouchEvent *ev = static_cast<QTouchEvent*>(e);
 | 
						|
		if (ev->device()->type() == QTouchDevice::TouchScreen) {
 | 
						|
			touchEvent(ev);
 | 
						|
  			return true;
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return QWidget::event(e);
 | 
						|
}
 | 
						|
 | 
						|
void HistoryInner::onTouchScrollTimer() {
 | 
						|
	uint64 nowTime = getms();
 | 
						|
	if (_touchScrollState == TouchScrollAcceleration && _touchWaitingAcceleration && (nowTime - _touchAccelerationTime) > 40) {
 | 
						|
		_touchScrollState = TouchScrollManual;
 | 
						|
		touchResetSpeed();
 | 
						|
	} else if (_touchScrollState == TouchScrollAuto || _touchScrollState == TouchScrollAcceleration) {
 | 
						|
		int32 elapsed = int32(nowTime - _touchTime);
 | 
						|
		QPoint delta = _touchSpeed * elapsed / 1000;
 | 
						|
		bool hasScrolled = _widget->touchScroll(delta);
 | 
						|
 | 
						|
		if (_touchSpeed.isNull() || !hasScrolled) {
 | 
						|
			_touchScrollState = TouchScrollManual;
 | 
						|
			_touchScroll = false;
 | 
						|
			_touchScrollTimer.stop();
 | 
						|
		} else {
 | 
						|
			_touchTime = nowTime;
 | 
						|
		}
 | 
						|
		touchDeaccelerate(elapsed);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryInner::touchUpdateSpeed() {
 | 
						|
	const uint64 nowTime = getms();
 | 
						|
	if (_touchPrevPosValid) {
 | 
						|
		const int elapsed = nowTime - _touchSpeedTime;
 | 
						|
		if (elapsed) {
 | 
						|
			const QPoint newPixelDiff = (_touchPos - _touchPrevPos);
 | 
						|
			const QPoint pixelsPerSecond = newPixelDiff * (1000 / elapsed);
 | 
						|
 | 
						|
			// fingers are inacurates, we ignore small changes to avoid stopping the autoscroll because
 | 
						|
			// of a small horizontal offset when scrolling vertically
 | 
						|
			const int newSpeedY = (qAbs(pixelsPerSecond.y()) > FingerAccuracyThreshold) ? pixelsPerSecond.y() : 0;
 | 
						|
			const int newSpeedX = (qAbs(pixelsPerSecond.x()) > FingerAccuracyThreshold) ? pixelsPerSecond.x() : 0;
 | 
						|
			if (_touchScrollState == TouchScrollAuto) {
 | 
						|
				const int oldSpeedY = _touchSpeed.y();
 | 
						|
				const int oldSpeedX = _touchSpeed.x();
 | 
						|
				if ((oldSpeedY <= 0 && newSpeedY <= 0) || ((oldSpeedY >= 0 && newSpeedY >= 0)
 | 
						|
					&& (oldSpeedX <= 0 && newSpeedX <= 0)) || (oldSpeedX >= 0 && newSpeedX >= 0)) {
 | 
						|
					_touchSpeed.setY(snap((oldSpeedY + (newSpeedY / 4)), -MaxScrollAccelerated, +MaxScrollAccelerated));
 | 
						|
					_touchSpeed.setX(snap((oldSpeedX + (newSpeedX / 4)), -MaxScrollAccelerated, +MaxScrollAccelerated));
 | 
						|
				} else {
 | 
						|
					_touchSpeed = QPoint();
 | 
						|
				}
 | 
						|
			} else {
 | 
						|
				// we average the speed to avoid strange effects with the last delta
 | 
						|
				if (!_touchSpeed.isNull()) {
 | 
						|
					_touchSpeed.setX(snap((_touchSpeed.x() / 4) + (newSpeedX * 3 / 4), -MaxScrollFlick, +MaxScrollFlick));
 | 
						|
					_touchSpeed.setY(snap((_touchSpeed.y() / 4) + (newSpeedY * 3 / 4), -MaxScrollFlick, +MaxScrollFlick));
 | 
						|
				} else {
 | 
						|
					_touchSpeed  = QPoint(newSpeedX, newSpeedY);
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	} else {
 | 
						|
		_touchPrevPosValid = true;
 | 
						|
	}
 | 
						|
	_touchSpeedTime = nowTime;
 | 
						|
	_touchPrevPos = _touchPos;
 | 
						|
}
 | 
						|
 | 
						|
void HistoryInner::touchResetSpeed() {
 | 
						|
	_touchSpeed = QPoint();
 | 
						|
	_touchPrevPosValid = false;
 | 
						|
}
 | 
						|
 | 
						|
void HistoryInner::touchDeaccelerate(int32 elapsed) {
 | 
						|
	int32 x = _touchSpeed.x();
 | 
						|
	int32 y = _touchSpeed.y();
 | 
						|
	_touchSpeed.setX((x == 0) ? x : (x > 0) ? qMax(0, x - elapsed) : qMin(0, x + elapsed));
 | 
						|
	_touchSpeed.setY((y == 0) ? y : (y > 0) ? qMax(0, y - elapsed) : qMin(0, y + elapsed));
 | 
						|
}
 | 
						|
 | 
						|
void HistoryInner::touchEvent(QTouchEvent *e) {
 | 
						|
	const Qt::TouchPointStates &states(e->touchPointStates());
 | 
						|
	if (e->type() == QEvent::TouchCancel) { // cancel
 | 
						|
		if (!_touchInProgress) return;
 | 
						|
		_touchInProgress = false;
 | 
						|
		_touchSelectTimer.stop();
 | 
						|
		_touchScroll = _touchSelect = false;
 | 
						|
		_touchScrollState = TouchScrollManual;
 | 
						|
		dragActionCancel();
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	if (!e->touchPoints().isEmpty()) {
 | 
						|
		_touchPrevPos = _touchPos;
 | 
						|
		_touchPos = e->touchPoints().cbegin()->screenPos().toPoint();
 | 
						|
	}
 | 
						|
 | 
						|
	switch (e->type()) {
 | 
						|
	case QEvent::TouchBegin:
 | 
						|
		if (_menu) {
 | 
						|
			e->accept();
 | 
						|
			return; // ignore mouse press, that was hiding context menu
 | 
						|
		}
 | 
						|
		if (_touchInProgress) return;
 | 
						|
		if (e->touchPoints().isEmpty()) return;
 | 
						|
 | 
						|
		_touchInProgress = true;
 | 
						|
		if (_touchScrollState == TouchScrollAuto) {
 | 
						|
			_touchScrollState = TouchScrollAcceleration;
 | 
						|
			_touchWaitingAcceleration = true;
 | 
						|
			_touchAccelerationTime = getms();
 | 
						|
			touchUpdateSpeed();
 | 
						|
			_touchStart = _touchPos;
 | 
						|
		} else {
 | 
						|
			_touchScroll = false;
 | 
						|
			_touchSelectTimer.start(QApplication::startDragTime());
 | 
						|
		}
 | 
						|
		_touchSelect = false;
 | 
						|
		_touchStart = _touchPrevPos = _touchPos;
 | 
						|
	break;
 | 
						|
 | 
						|
	case QEvent::TouchUpdate:
 | 
						|
		if (!_touchInProgress) return;
 | 
						|
		if (_touchSelect) {
 | 
						|
			dragActionUpdate(_touchPos);
 | 
						|
		} else if (!_touchScroll && (_touchPos - _touchStart).manhattanLength() >= QApplication::startDragDistance()) {
 | 
						|
			_touchSelectTimer.stop();
 | 
						|
			_touchScroll = true;
 | 
						|
			touchUpdateSpeed();
 | 
						|
		}
 | 
						|
		if (_touchScroll) {
 | 
						|
			if (_touchScrollState == TouchScrollManual) {
 | 
						|
				touchScrollUpdated(_touchPos);
 | 
						|
			} else if (_touchScrollState == TouchScrollAcceleration) {
 | 
						|
				touchUpdateSpeed();
 | 
						|
				_touchAccelerationTime = getms();
 | 
						|
				if (_touchSpeed.isNull()) {
 | 
						|
					_touchScrollState = TouchScrollManual;
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	break;
 | 
						|
 | 
						|
	case QEvent::TouchEnd:
 | 
						|
		if (!_touchInProgress) return;
 | 
						|
		_touchInProgress = false;
 | 
						|
		if (_touchSelect) {
 | 
						|
			dragActionFinish(_touchPos, Qt::RightButton);
 | 
						|
			QContextMenuEvent contextMenu(QContextMenuEvent::Mouse, mapFromGlobal(_touchPos), _touchPos);
 | 
						|
			showContextMenu(&contextMenu, true);
 | 
						|
			_touchScroll = false;
 | 
						|
		} else if (_touchScroll) {
 | 
						|
			if (_touchScrollState == TouchScrollManual) {
 | 
						|
				_touchScrollState = TouchScrollAuto;
 | 
						|
				_touchPrevPosValid = false;
 | 
						|
				_touchScrollTimer.start(15);
 | 
						|
				_touchTime = getms();
 | 
						|
			} else if (_touchScrollState == TouchScrollAuto) {
 | 
						|
				_touchScrollState = TouchScrollManual;
 | 
						|
				_touchScroll = false;
 | 
						|
				touchResetSpeed();
 | 
						|
			} else if (_touchScrollState == TouchScrollAcceleration) {
 | 
						|
				_touchScrollState = TouchScrollAuto;
 | 
						|
				_touchWaitingAcceleration = false;
 | 
						|
				_touchPrevPosValid = false;
 | 
						|
			}
 | 
						|
		} else { // one short tap -- like mouse click
 | 
						|
			dragActionStart(_touchPos);
 | 
						|
			dragActionFinish(_touchPos);
 | 
						|
		}
 | 
						|
		_touchSelectTimer.stop();
 | 
						|
		_touchSelect = false;
 | 
						|
		break;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryInner::mouseMoveEvent(QMouseEvent *e) {
 | 
						|
	if (!(e->buttons() & (Qt::LeftButton | Qt::MiddleButton)) && _dragAction != NoDrag) {
 | 
						|
		mouseReleaseEvent(e);
 | 
						|
	}
 | 
						|
	dragActionUpdate(e->globalPos());
 | 
						|
}
 | 
						|
 | 
						|
void HistoryInner::dragActionUpdate(const QPoint &screenPos) {
 | 
						|
	_dragPos = screenPos;
 | 
						|
	onUpdateSelected();
 | 
						|
}
 | 
						|
 | 
						|
void HistoryInner::touchScrollUpdated(const QPoint &screenPos) {
 | 
						|
	_touchPos = screenPos;
 | 
						|
	_widget->touchScroll(_touchPos - _touchPrevPos);
 | 
						|
	touchUpdateSpeed();
 | 
						|
}
 | 
						|
 | 
						|
QPoint HistoryInner::mapMouseToItem(QPoint p, HistoryItem *item) {
 | 
						|
	int32 msgy = itemTop(item);
 | 
						|
	if (msgy < 0) return QPoint(0, 0);
 | 
						|
 | 
						|
	p.setY(p.y() - msgy);
 | 
						|
	return p;
 | 
						|
}
 | 
						|
 | 
						|
void HistoryInner::mousePressEvent(QMouseEvent *e) {
 | 
						|
	if (_menu) {
 | 
						|
		e->accept();
 | 
						|
		return; // ignore mouse press, that was hiding context menu
 | 
						|
	}
 | 
						|
	dragActionStart(e->globalPos(), e->button());
 | 
						|
}
 | 
						|
 | 
						|
void HistoryInner::dragActionStart(const QPoint &screenPos, Qt::MouseButton button) {
 | 
						|
	dragActionUpdate(screenPos);
 | 
						|
	if (button != Qt::LeftButton) return;
 | 
						|
 | 
						|
	ClickHandler::pressed();
 | 
						|
	if (App::pressedItem() != App::hoveredItem()) {
 | 
						|
		repaintItem(App::pressedItem());
 | 
						|
		App::pressedItem(App::hoveredItem());
 | 
						|
		repaintItem(App::pressedItem());
 | 
						|
	}
 | 
						|
 | 
						|
	_dragAction = NoDrag;
 | 
						|
	_dragItem = App::mousedItem();
 | 
						|
	_dragStartPos = mapMouseToItem(mapFromGlobal(screenPos), _dragItem);
 | 
						|
	_dragWasInactive = App::wnd()->inactivePress();
 | 
						|
	if (_dragWasInactive) App::wnd()->inactivePress(false);
 | 
						|
 | 
						|
	if (ClickHandler::getPressed()) {
 | 
						|
		_dragAction = PrepareDrag;
 | 
						|
	} else if (!_selected.isEmpty()) {
 | 
						|
		if (_selected.cbegin().value() == FullSelection) {
 | 
						|
			if (_selected.constFind(_dragItem) != _selected.cend() && App::hoveredItem()) {
 | 
						|
				_dragAction = PrepareDrag; // start items drag
 | 
						|
			} else if (!_dragWasInactive) {
 | 
						|
				_dragAction = PrepareSelect; // start items select
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if (_dragAction == NoDrag && _dragItem) {
 | 
						|
		HistoryTextState dragState;
 | 
						|
		if (_trippleClickTimer.isActive() && (screenPos - _trippleClickPoint).manhattanLength() < QApplication::startDragDistance()) {
 | 
						|
			HistoryStateRequest request;
 | 
						|
			request.flags = Text::StateRequest::Flag::LookupSymbol;
 | 
						|
			dragState = _dragItem->getState(_dragStartPos.x(), _dragStartPos.y(), request);
 | 
						|
			if (dragState.cursor == HistoryInTextCursorState) {
 | 
						|
				TextSelection selStatus = { dragState.symbol, dragState.symbol };
 | 
						|
				if (selStatus != FullSelection && (_selected.isEmpty() || _selected.cbegin().value() != FullSelection)) {
 | 
						|
					if (!_selected.isEmpty()) {
 | 
						|
						repaintItem(_selected.cbegin().key());
 | 
						|
						_selected.clear();
 | 
						|
					}
 | 
						|
					_selected.insert(_dragItem, selStatus);
 | 
						|
					_dragSymbol = dragState.symbol;
 | 
						|
					_dragAction = Selecting;
 | 
						|
					_dragSelType = TextSelectType::Paragraphs;
 | 
						|
					dragActionUpdate(_dragPos);
 | 
						|
				    _trippleClickTimer.start(QApplication::doubleClickInterval());
 | 
						|
				}
 | 
						|
			}
 | 
						|
		} else if (App::pressedItem()) {
 | 
						|
			HistoryStateRequest request;
 | 
						|
			request.flags = Text::StateRequest::Flag::LookupSymbol;
 | 
						|
			dragState = _dragItem->getState(_dragStartPos.x(), _dragStartPos.y(), request);
 | 
						|
		}
 | 
						|
		if (_dragSelType != TextSelectType::Paragraphs) {
 | 
						|
			if (App::pressedItem()) {
 | 
						|
				_dragSymbol = dragState.symbol;
 | 
						|
				bool uponSelected = (dragState.cursor == HistoryInTextCursorState);
 | 
						|
				if (uponSelected) {
 | 
						|
					if (_selected.isEmpty() ||
 | 
						|
						_selected.cbegin().value() == FullSelection ||
 | 
						|
						_selected.cbegin().key() != _dragItem
 | 
						|
					) {
 | 
						|
						uponSelected = false;
 | 
						|
					} else {
 | 
						|
						uint16 selFrom = _selected.cbegin().value().from, selTo = _selected.cbegin().value().to;
 | 
						|
						if (_dragSymbol < selFrom || _dragSymbol >= selTo) {
 | 
						|
							uponSelected = false;
 | 
						|
						}
 | 
						|
					}
 | 
						|
				}
 | 
						|
				if (uponSelected) {
 | 
						|
					_dragAction = PrepareDrag; // start text drag
 | 
						|
				} else if (!_dragWasInactive) {
 | 
						|
					if (dynamic_cast<HistorySticker*>(App::pressedItem()->getMedia()) || _dragCursorState == HistoryInDateCursorState) {
 | 
						|
						_dragAction = PrepareDrag; // start sticker drag or by-date drag
 | 
						|
					} else {
 | 
						|
						if (dragState.afterSymbol) ++_dragSymbol;
 | 
						|
						TextSelection selStatus = { _dragSymbol, _dragSymbol };
 | 
						|
						if (selStatus != FullSelection && (_selected.isEmpty() || _selected.cbegin().value() != FullSelection)) {
 | 
						|
							if (!_selected.isEmpty()) {
 | 
						|
								repaintItem(_selected.cbegin().key());
 | 
						|
								_selected.clear();
 | 
						|
							}
 | 
						|
							_selected.insert(_dragItem, selStatus);
 | 
						|
							_dragAction = Selecting;
 | 
						|
							repaintItem(_dragItem);
 | 
						|
						} else {
 | 
						|
							_dragAction = PrepareSelect;
 | 
						|
						}
 | 
						|
					}
 | 
						|
				}
 | 
						|
			} else if (!_dragWasInactive) {
 | 
						|
				_dragAction = PrepareSelect; // start items select
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if (!_dragItem) {
 | 
						|
		_dragAction = NoDrag;
 | 
						|
	} else if (_dragAction == NoDrag) {
 | 
						|
		_dragItem = nullptr;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryInner::dragActionCancel() {
 | 
						|
	_dragItem = 0;
 | 
						|
	_dragAction = NoDrag;
 | 
						|
	_dragStartPos = QPoint(0, 0);
 | 
						|
	_dragSelFrom = _dragSelTo = 0;
 | 
						|
	_wasSelectedText = false;
 | 
						|
	_widget->noSelectingScroll();
 | 
						|
}
 | 
						|
 | 
						|
void HistoryInner::onDragExec() {
 | 
						|
	if (_dragAction != Dragging) return;
 | 
						|
 | 
						|
	bool uponSelected = false;
 | 
						|
	if (_dragItem) {
 | 
						|
		if (!_selected.isEmpty() && _selected.cbegin().value() == FullSelection) {
 | 
						|
			uponSelected = _selected.contains(_dragItem);
 | 
						|
		} else {
 | 
						|
			HistoryStateRequest request;
 | 
						|
			request.flags |= Text::StateRequest::Flag::LookupSymbol;
 | 
						|
			auto dragState = _dragItem->getState(_dragStartPos.x(), _dragStartPos.y(), request);
 | 
						|
			uponSelected = (dragState.cursor == HistoryInTextCursorState);
 | 
						|
			if (uponSelected) {
 | 
						|
				if (_selected.isEmpty() ||
 | 
						|
					_selected.cbegin().value() == FullSelection ||
 | 
						|
					_selected.cbegin().key() != _dragItem
 | 
						|
					) {
 | 
						|
					uponSelected = false;
 | 
						|
				} else {
 | 
						|
					uint16 selFrom = _selected.cbegin().value().from, selTo = _selected.cbegin().value().to;
 | 
						|
					if (dragState.symbol < selFrom || dragState.symbol >= selTo) {
 | 
						|
						uponSelected = false;
 | 
						|
					}
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	ClickHandlerPtr pressedHandler = ClickHandler::getPressed();
 | 
						|
	TextWithEntities sel;
 | 
						|
	QList<QUrl> urls;
 | 
						|
	if (uponSelected) {
 | 
						|
		sel = getSelectedText();
 | 
						|
	} else if (pressedHandler) {
 | 
						|
		sel = { pressedHandler->dragText(), EntitiesInText() };
 | 
						|
		//if (!sel.isEmpty() && sel.at(0) != '/' && sel.at(0) != '@' && sel.at(0) != '#') {
 | 
						|
		//	urls.push_back(QUrl::fromEncoded(sel.toUtf8())); // Google Chrome crashes in Mac OS X O_o
 | 
						|
		//}
 | 
						|
	}
 | 
						|
	if (auto mimeData = mimeDataFromTextWithEntities(sel)) {
 | 
						|
		updateDragSelection(0, 0, false);
 | 
						|
		_widget->noSelectingScroll();
 | 
						|
 | 
						|
		QDrag *drag = new QDrag(App::wnd());
 | 
						|
		if (!urls.isEmpty()) mimeData->setUrls(urls);
 | 
						|
		if (uponSelected && !_selected.isEmpty() && _selected.cbegin().value() == FullSelection && !Adaptive::OneColumn()) {
 | 
						|
			mimeData->setData(qsl("application/x-td-forward-selected"), "1");
 | 
						|
		}
 | 
						|
		drag->setMimeData(mimeData);
 | 
						|
		drag->exec(Qt::CopyAction);
 | 
						|
 | 
						|
		// We don't receive mouseReleaseEvent when drag is finished.
 | 
						|
		ClickHandler::unpressed();
 | 
						|
		if (App::main()) App::main()->updateAfterDrag();
 | 
						|
		return;
 | 
						|
	} else {
 | 
						|
		QString forwardMimeType;
 | 
						|
		HistoryMedia *pressedMedia = nullptr;
 | 
						|
		if (HistoryItem *pressedItem = App::pressedItem()) {
 | 
						|
			pressedMedia = pressedItem->getMedia();
 | 
						|
			if (_dragCursorState == HistoryInDateCursorState || (pressedMedia && pressedMedia->dragItem())) {
 | 
						|
				forwardMimeType = qsl("application/x-td-forward-pressed");
 | 
						|
			}
 | 
						|
		}
 | 
						|
		if (HistoryItem *pressedLnkItem = App::pressedLinkItem()) {
 | 
						|
			if ((pressedMedia = pressedLnkItem->getMedia())) {
 | 
						|
				if (forwardMimeType.isEmpty() && pressedMedia->dragItemByHandler(pressedHandler)) {
 | 
						|
					forwardMimeType = qsl("application/x-td-forward-pressed-link");
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
		if (!forwardMimeType.isEmpty()) {
 | 
						|
			QDrag *drag = new QDrag(App::wnd());
 | 
						|
			QMimeData *mimeData = new QMimeData();
 | 
						|
 | 
						|
			mimeData->setData(forwardMimeType, "1");
 | 
						|
			if (DocumentData *document = (pressedMedia ? pressedMedia->getDocument() : nullptr)) {
 | 
						|
				QString filepath = document->filepath(DocumentData::FilePathResolveChecked);
 | 
						|
				if (!filepath.isEmpty()) {
 | 
						|
					QList<QUrl> urls;
 | 
						|
					urls.push_back(QUrl::fromLocalFile(filepath));
 | 
						|
					mimeData->setUrls(urls);
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
			drag->setMimeData(mimeData);
 | 
						|
			drag->exec(Qt::CopyAction);
 | 
						|
 | 
						|
			// We don't receive mouseReleaseEvent when drag is finished.
 | 
						|
			ClickHandler::unpressed();
 | 
						|
			if (App::main()) App::main()->updateAfterDrag();
 | 
						|
			return;
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryInner::itemRemoved(HistoryItem *item) {
 | 
						|
	if (_history != item->history() && _migrated != item->history()) {
 | 
						|
		return;
 | 
						|
	}
 | 
						|
	if (!App::main()) {
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	auto i = _selected.find(item);
 | 
						|
	if (i != _selected.cend()) {
 | 
						|
		_selected.erase(i);
 | 
						|
		_widget->updateTopBarSelection();
 | 
						|
	}
 | 
						|
 | 
						|
	if (_dragItem == item) {
 | 
						|
		dragActionCancel();
 | 
						|
	}
 | 
						|
 | 
						|
	if (_dragSelFrom == item || _dragSelTo == item) {
 | 
						|
		_dragSelFrom = 0;
 | 
						|
		_dragSelTo = 0;
 | 
						|
		update();
 | 
						|
	}
 | 
						|
	onUpdateSelected();
 | 
						|
}
 | 
						|
 | 
						|
void HistoryInner::dragActionFinish(const QPoint &screenPos, Qt::MouseButton button) {
 | 
						|
	dragActionUpdate(screenPos);
 | 
						|
 | 
						|
	ClickHandlerPtr activated = ClickHandler::unpressed();
 | 
						|
	if (_dragAction == Dragging) {
 | 
						|
		activated.clear();
 | 
						|
	} else if (HistoryItem *pressed = App::pressedLinkItem()) {
 | 
						|
		// if we are in selecting items mode perhaps we want to
 | 
						|
		// toggle selection instead of activating the pressed link
 | 
						|
		if (_dragAction == PrepareDrag && !_dragWasInactive && !_selected.isEmpty() && _selected.cbegin().value() == FullSelection && button != Qt::RightButton) {
 | 
						|
			if (HistoryMedia *media = pressed->getMedia()) {
 | 
						|
				if (media->toggleSelectionByHandlerClick(activated)) {
 | 
						|
					activated.clear();
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if (App::pressedItem()) {
 | 
						|
		repaintItem(App::pressedItem());
 | 
						|
		App::pressedItem(nullptr);
 | 
						|
	}
 | 
						|
 | 
						|
	_wasSelectedText = false;
 | 
						|
 | 
						|
	if (activated) {
 | 
						|
		dragActionCancel();
 | 
						|
		App::activateClickHandler(activated, button);
 | 
						|
		return;
 | 
						|
	}
 | 
						|
	if (_dragAction == PrepareSelect && !_dragWasInactive && !_selected.isEmpty() && _selected.cbegin().value() == FullSelection) {
 | 
						|
		SelectedItems::iterator i = _selected.find(_dragItem);
 | 
						|
		if (i == _selected.cend() && !_dragItem->serviceMsg() && _dragItem->id > 0) {
 | 
						|
			if (_selected.size() < MaxSelectedItems) {
 | 
						|
				if (!_selected.isEmpty() && _selected.cbegin().value() != FullSelection) {
 | 
						|
					_selected.clear();
 | 
						|
				}
 | 
						|
				_selected.insert(_dragItem, FullSelection);
 | 
						|
			}
 | 
						|
		} else {
 | 
						|
			_selected.erase(i);
 | 
						|
		}
 | 
						|
		repaintItem(_dragItem);
 | 
						|
	} else if (_dragAction == PrepareDrag && !_dragWasInactive && button != Qt::RightButton) {
 | 
						|
		SelectedItems::iterator i = _selected.find(_dragItem);
 | 
						|
		if (i != _selected.cend() && i.value() == FullSelection) {
 | 
						|
			_selected.erase(i);
 | 
						|
			repaintItem(_dragItem);
 | 
						|
		} else if (i == _selected.cend() && !_dragItem->serviceMsg() && _dragItem->id > 0 && !_selected.isEmpty() && _selected.cbegin().value() == FullSelection) {
 | 
						|
			if (_selected.size() < MaxSelectedItems) {
 | 
						|
				_selected.insert(_dragItem, FullSelection);
 | 
						|
				repaintItem(_dragItem);
 | 
						|
			}
 | 
						|
		} else {
 | 
						|
			_selected.clear();
 | 
						|
			update();
 | 
						|
		}
 | 
						|
	} else if (_dragAction == Selecting) {
 | 
						|
		if (_dragSelFrom && _dragSelTo) {
 | 
						|
			applyDragSelection();
 | 
						|
			_dragSelFrom = _dragSelTo = 0;
 | 
						|
		} else if (!_selected.isEmpty() && !_dragWasInactive) {
 | 
						|
			auto sel = _selected.cbegin().value();
 | 
						|
			if (sel != FullSelection && sel.from == sel.to) {
 | 
						|
				_selected.clear();
 | 
						|
				if (App::wnd()) App::wnd()->setInnerFocus();
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	_dragAction = NoDrag;
 | 
						|
	_dragItem = 0;
 | 
						|
	_dragSelType = TextSelectType::Letters;
 | 
						|
	_widget->noSelectingScroll();
 | 
						|
	_widget->updateTopBarSelection();
 | 
						|
 | 
						|
#if defined Q_OS_LINUX32 || defined Q_OS_LINUX64
 | 
						|
	if (!_selected.isEmpty() && _selected.cbegin().value() != FullSelection) {
 | 
						|
		setToClipboard(_selected.cbegin().key()->selectedText(_selected.cbegin().value()), QClipboard::Selection);
 | 
						|
	}
 | 
						|
#endif // Q_OS_LINUX32 || Q_OS_LINUX64
 | 
						|
}
 | 
						|
 | 
						|
void HistoryInner::mouseReleaseEvent(QMouseEvent *e) {
 | 
						|
	dragActionFinish(e->globalPos(), e->button());
 | 
						|
	if (!rect().contains(e->pos())) {
 | 
						|
		leaveEvent(e);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryInner::mouseDoubleClickEvent(QMouseEvent *e) {
 | 
						|
	if (!_history) return;
 | 
						|
 | 
						|
	dragActionStart(e->globalPos(), e->button());
 | 
						|
	if (((_dragAction == Selecting && !_selected.isEmpty() && _selected.cbegin().value() != FullSelection) || (_dragAction == NoDrag && (_selected.isEmpty() || _selected.cbegin().value() != FullSelection))) && _dragSelType == TextSelectType::Letters && _dragItem) {
 | 
						|
		HistoryStateRequest request;
 | 
						|
		request.flags |= Text::StateRequest::Flag::LookupSymbol;
 | 
						|
		auto dragState = _dragItem->getState(_dragStartPos.x(), _dragStartPos.y(), request);
 | 
						|
		if (dragState.cursor == HistoryInTextCursorState) {
 | 
						|
			_dragSymbol = dragState.symbol;
 | 
						|
			_dragSelType = TextSelectType::Words;
 | 
						|
			if (_dragAction == NoDrag) {
 | 
						|
				_dragAction = Selecting;
 | 
						|
				TextSelection selStatus = { dragState.symbol, dragState.symbol };
 | 
						|
				if (!_selected.isEmpty()) {
 | 
						|
					repaintItem(_selected.cbegin().key());
 | 
						|
					_selected.clear();
 | 
						|
				}
 | 
						|
				_selected.insert(_dragItem, selStatus);
 | 
						|
			}
 | 
						|
			mouseMoveEvent(e);
 | 
						|
 | 
						|
	        _trippleClickPoint = e->globalPos();
 | 
						|
	        _trippleClickTimer.start(QApplication::doubleClickInterval());
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
 | 
						|
	if (_menu) {
 | 
						|
		_menu->deleteLater();
 | 
						|
		_menu = 0;
 | 
						|
	}
 | 
						|
	if (e->reason() == QContextMenuEvent::Mouse) {
 | 
						|
		dragActionUpdate(e->globalPos());
 | 
						|
	}
 | 
						|
 | 
						|
	int32 selectedForForward, selectedForDelete;
 | 
						|
	getSelectionState(selectedForForward, selectedForDelete);
 | 
						|
	bool canSendMessages = _widget->canSendMessages(_peer);
 | 
						|
 | 
						|
	// -2 - has full selected items, but not over, -1 - has selection, but no over, 0 - no selection, 1 - over text, 2 - over full selected items
 | 
						|
	int32 isUponSelected = 0, hasSelected = 0;;
 | 
						|
	if (!_selected.isEmpty()) {
 | 
						|
		isUponSelected = -1;
 | 
						|
		if (_selected.cbegin().value() == FullSelection) {
 | 
						|
			hasSelected = 2;
 | 
						|
			if (App::hoveredItem() && _selected.constFind(App::hoveredItem()) != _selected.cend()) {
 | 
						|
				isUponSelected = 2;
 | 
						|
			} else {
 | 
						|
				isUponSelected = -2;
 | 
						|
			}
 | 
						|
		} else {
 | 
						|
			uint16 selFrom = _selected.cbegin().value().from, selTo = _selected.cbegin().value().to;
 | 
						|
			hasSelected = (selTo > selFrom) ? 1 : 0;
 | 
						|
			if (App::mousedItem() && App::mousedItem() == App::hoveredItem()) {
 | 
						|
				QPoint mousePos(mapMouseToItem(mapFromGlobal(_dragPos), App::mousedItem()));
 | 
						|
				HistoryStateRequest request;
 | 
						|
				request.flags |= Text::StateRequest::Flag::LookupSymbol;
 | 
						|
				auto dragState = App::mousedItem()->getState(mousePos.x(), mousePos.y(), request);
 | 
						|
				if (dragState.cursor == HistoryInTextCursorState && dragState.symbol >= selFrom && dragState.symbol < selTo) {
 | 
						|
					isUponSelected = 1;
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if (showFromTouch && hasSelected && isUponSelected < hasSelected) {
 | 
						|
		isUponSelected = hasSelected;
 | 
						|
	}
 | 
						|
 | 
						|
	_menu = new PopupMenu();
 | 
						|
 | 
						|
	_contextMenuLnk = ClickHandler::getActive();
 | 
						|
	HistoryItem *item = App::hoveredItem() ? App::hoveredItem() : App::hoveredLinkItem();
 | 
						|
	PhotoClickHandler *lnkPhoto = dynamic_cast<PhotoClickHandler*>(_contextMenuLnk.data());
 | 
						|
    DocumentClickHandler *lnkDocument = dynamic_cast<DocumentClickHandler*>(_contextMenuLnk.data());
 | 
						|
	bool lnkIsVideo = lnkDocument ? lnkDocument->document()->isVideo() : false;
 | 
						|
	bool lnkIsAudio = lnkDocument ? (lnkDocument->document()->voice() != nullptr) : false;
 | 
						|
	bool lnkIsSong = lnkDocument ? (lnkDocument->document()->song() != nullptr) : false;
 | 
						|
	if (lnkPhoto || lnkDocument) {
 | 
						|
		if (isUponSelected > 0) {
 | 
						|
			_menu->addAction(lang(lng_context_copy_selected), this, SLOT(copySelectedText()))->setEnabled(true);
 | 
						|
		}
 | 
						|
		if (item && item->id > 0 && isUponSelected != 2 && isUponSelected != -2) {
 | 
						|
			if (canSendMessages) {
 | 
						|
				_menu->addAction(lang(lng_context_reply_msg), _widget, SLOT(onReplyToMessage()));
 | 
						|
			}
 | 
						|
			if (item->canEdit(::date(unixtime()))) {
 | 
						|
				_menu->addAction(lang(lng_context_edit_msg), _widget, SLOT(onEditMessage()));
 | 
						|
			}
 | 
						|
			if (item->canPin()) {
 | 
						|
				bool ispinned = (item->history()->peer->asChannel()->mgInfo->pinnedMsgId == item->id);
 | 
						|
				_menu->addAction(lang(ispinned ? lng_context_unpin_msg : lng_context_pin_msg), _widget, ispinned ? SLOT(onUnpinMessage()) : SLOT(onPinMessage()));
 | 
						|
			}
 | 
						|
		}
 | 
						|
		if (lnkPhoto) {
 | 
						|
			_menu->addAction(lang(lng_context_save_image), this, SLOT(saveContextImage()))->setEnabled(true);
 | 
						|
			_menu->addAction(lang(lng_context_copy_image), this, SLOT(copyContextImage()))->setEnabled(true);
 | 
						|
		} else {
 | 
						|
			if (lnkDocument && lnkDocument->document()->loading()) {
 | 
						|
				_menu->addAction(lang(lng_context_cancel_download), this, SLOT(cancelContextDownload()))->setEnabled(true);
 | 
						|
			} else {
 | 
						|
				if (lnkDocument && lnkDocument->document()->loaded() && lnkDocument->document()->isGifv()) {
 | 
						|
					_menu->addAction(lang(lng_context_save_gif), this, SLOT(saveContextGif()))->setEnabled(true);
 | 
						|
				}
 | 
						|
				if (lnkDocument && !lnkDocument->document()->filepath(DocumentData::FilePathResolveChecked).isEmpty()) {
 | 
						|
					_menu->addAction(lang((cPlatform() == dbipMac || cPlatform() == dbipMacOld) ? lng_context_show_in_finder : lng_context_show_in_folder), this, SLOT(showContextInFolder()))->setEnabled(true);
 | 
						|
				}
 | 
						|
				_menu->addAction(lang(lnkIsVideo ? lng_context_save_video : (lnkIsAudio ? lng_context_save_audio : (lnkIsSong ? lng_context_save_audio_file : lng_context_save_file))), this, SLOT(saveContextFile()))->setEnabled(true);
 | 
						|
			}
 | 
						|
		}
 | 
						|
		if (item && item->hasDirectLink() && isUponSelected != 2 && isUponSelected != -2) {
 | 
						|
			_menu->addAction(lang(lng_context_copy_post_link), _widget, SLOT(onCopyPostLink()));
 | 
						|
		}
 | 
						|
		if (isUponSelected > 1) {
 | 
						|
			_menu->addAction(lang(lng_context_forward_selected), _widget, SLOT(onForwardSelected()));
 | 
						|
			if (selectedForDelete == selectedForForward) {
 | 
						|
				_menu->addAction(lang(lng_context_delete_selected), _widget, SLOT(onDeleteSelected()));
 | 
						|
			}
 | 
						|
			_menu->addAction(lang(lng_context_clear_selection), _widget, SLOT(onClearSelected()));
 | 
						|
		} else if (App::hoveredLinkItem()) {
 | 
						|
			if (isUponSelected != -2) {
 | 
						|
				if (dynamic_cast<HistoryMessage*>(App::hoveredLinkItem()) && App::hoveredLinkItem()->id > 0) {
 | 
						|
					_menu->addAction(lang(lng_context_forward_msg), _widget, SLOT(forwardMessage()))->setEnabled(true);
 | 
						|
				}
 | 
						|
				if (App::hoveredLinkItem()->canDelete()) {
 | 
						|
					_menu->addAction(lang(lng_context_delete_msg), _widget, SLOT(deleteMessage()))->setEnabled(true);
 | 
						|
				}
 | 
						|
			}
 | 
						|
			if (App::hoveredLinkItem()->id > 0 && !App::hoveredLinkItem()->serviceMsg()) {
 | 
						|
				_menu->addAction(lang(lng_context_select_msg), _widget, SLOT(selectMessage()))->setEnabled(true);
 | 
						|
			}
 | 
						|
			App::contextItem(App::hoveredLinkItem());
 | 
						|
		}
 | 
						|
	} else { // maybe cursor on some text history item?
 | 
						|
		bool canDelete = (item && item->type() == HistoryItemMsg) && item->canDelete();
 | 
						|
		bool canForward = (item && item->type() == HistoryItemMsg) && (item->id > 0) && !item->serviceMsg();
 | 
						|
 | 
						|
		HistoryMessage *msg = dynamic_cast<HistoryMessage*>(item);
 | 
						|
		if (isUponSelected > 0) {
 | 
						|
			_menu->addAction(lang(lng_context_copy_selected), this, SLOT(copySelectedText()))->setEnabled(true);
 | 
						|
			if (item && item->id > 0 && isUponSelected != 2) {
 | 
						|
				if (canSendMessages) {
 | 
						|
					_menu->addAction(lang(lng_context_reply_msg), _widget, SLOT(onReplyToMessage()));
 | 
						|
				}
 | 
						|
				if (item->canEdit(::date(unixtime()))) {
 | 
						|
					_menu->addAction(lang(lng_context_edit_msg), _widget, SLOT(onEditMessage()));
 | 
						|
				}
 | 
						|
				if (item->canPin()) {
 | 
						|
					bool ispinned = (item->history()->peer->asChannel()->mgInfo->pinnedMsgId == item->id);
 | 
						|
					_menu->addAction(lang(ispinned ? lng_context_unpin_msg : lng_context_pin_msg), _widget, ispinned ? SLOT(onUnpinMessage()) : SLOT(onPinMessage()));
 | 
						|
				}
 | 
						|
			}
 | 
						|
		} else {
 | 
						|
			if (item && item->id > 0 && isUponSelected != -2) {
 | 
						|
				if (canSendMessages) {
 | 
						|
					_menu->addAction(lang(lng_context_reply_msg), _widget, SLOT(onReplyToMessage()));
 | 
						|
				}
 | 
						|
				if (item->canEdit(::date(unixtime()))) {
 | 
						|
					_menu->addAction(lang(lng_context_edit_msg), _widget, SLOT(onEditMessage()));
 | 
						|
				}
 | 
						|
				if (item->canPin()) {
 | 
						|
					bool ispinned = (item->history()->peer->asChannel()->mgInfo->pinnedMsgId == item->id);
 | 
						|
					_menu->addAction(lang(ispinned ? lng_context_unpin_msg : lng_context_pin_msg), _widget, ispinned ? SLOT(onUnpinMessage()) : SLOT(onPinMessage()));
 | 
						|
				}
 | 
						|
			}
 | 
						|
			if (item && !isUponSelected) {
 | 
						|
				bool mediaHasTextForCopy = false;
 | 
						|
				if (HistoryMedia *media = (msg ? msg->getMedia() : nullptr)) {
 | 
						|
					mediaHasTextForCopy = media->hasTextForCopy();
 | 
						|
					if (media->type() == MediaTypeWebPage && static_cast<HistoryWebPage*>(media)->attach()) {
 | 
						|
						media = static_cast<HistoryWebPage*>(media)->attach();
 | 
						|
					}
 | 
						|
					if (media->type() == MediaTypeSticker) {
 | 
						|
						DocumentData *doc = media->getDocument();
 | 
						|
						if (doc && doc->sticker() && doc->sticker()->set.type() != mtpc_inputStickerSetEmpty) {
 | 
						|
							_menu->addAction(lang(doc->sticker()->setInstalled() ? lng_context_pack_info : lng_context_pack_add), _widget, SLOT(onStickerPackInfo()));
 | 
						|
						}
 | 
						|
 | 
						|
						_menu->addAction(lang(lng_context_save_image), this, SLOT(saveContextFile()))->setEnabled(true);
 | 
						|
					} else if (media->type() == MediaTypeGif && !_contextMenuLnk) {
 | 
						|
						DocumentData *doc = media->getDocument();
 | 
						|
						if (doc) {
 | 
						|
							if (doc->loading()) {
 | 
						|
								_menu->addAction(lang(lng_context_cancel_download), this, SLOT(cancelContextDownload()))->setEnabled(true);
 | 
						|
							} else {
 | 
						|
								if (doc->isGifv()) {
 | 
						|
									_menu->addAction(lang(lng_context_save_gif), this, SLOT(saveContextGif()))->setEnabled(true);
 | 
						|
								}
 | 
						|
								if (!doc->filepath(DocumentData::FilePathResolveChecked).isEmpty()) {
 | 
						|
									_menu->addAction(lang((cPlatform() == dbipMac || cPlatform() == dbipMacOld) ? lng_context_show_in_finder : lng_context_show_in_folder), this, SLOT(showContextInFolder()))->setEnabled(true);
 | 
						|
								}
 | 
						|
								_menu->addAction(lang(lng_context_save_file), this, SLOT(saveContextFile()))->setEnabled(true);
 | 
						|
							}
 | 
						|
						}
 | 
						|
					}
 | 
						|
				}
 | 
						|
				if (msg && !_contextMenuLnk && (!msg->emptyText() || mediaHasTextForCopy)) {
 | 
						|
					_menu->addAction(lang(lng_context_copy_text), this, SLOT(copyContextText()))->setEnabled(true);
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		QString linkCopyToClipboardText = _contextMenuLnk ? _contextMenuLnk->copyToClipboardContextItemText() : QString();
 | 
						|
		if (!linkCopyToClipboardText.isEmpty()) {
 | 
						|
			_menu->addAction(linkCopyToClipboardText, this, SLOT(copyContextUrl()))->setEnabled(true);
 | 
						|
		}
 | 
						|
		if (item && item->hasDirectLink() && isUponSelected != 2 && isUponSelected != -2) {
 | 
						|
			_menu->addAction(lang(lng_context_copy_post_link), _widget, SLOT(onCopyPostLink()));
 | 
						|
		}
 | 
						|
		if (isUponSelected > 1) {
 | 
						|
			_menu->addAction(lang(lng_context_forward_selected), _widget, SLOT(onForwardSelected()));
 | 
						|
			if (selectedForDelete == selectedForForward) {
 | 
						|
				_menu->addAction(lang(lng_context_delete_selected), _widget, SLOT(onDeleteSelected()));
 | 
						|
			}
 | 
						|
			_menu->addAction(lang(lng_context_clear_selection), _widget, SLOT(onClearSelected()));
 | 
						|
		} else if (item && ((isUponSelected != -2 && (canForward || canDelete)) || item->id > 0)) {
 | 
						|
			if (isUponSelected != -2) {
 | 
						|
				if (canForward) {
 | 
						|
					_menu->addAction(lang(lng_context_forward_msg), _widget, SLOT(forwardMessage()))->setEnabled(true);
 | 
						|
				}
 | 
						|
 | 
						|
				if (canDelete) {
 | 
						|
					_menu->addAction(lang((msg && msg->uploading()) ? lng_context_cancel_upload : lng_context_delete_msg), _widget, SLOT(deleteMessage()))->setEnabled(true);
 | 
						|
				}
 | 
						|
			}
 | 
						|
			if (item->id > 0 && !item->serviceMsg()) {
 | 
						|
				_menu->addAction(lang(lng_context_select_msg), _widget, SLOT(selectMessage()))->setEnabled(true);
 | 
						|
			}
 | 
						|
		} else {
 | 
						|
			if (App::mousedItem() && !App::mousedItem()->serviceMsg() && App::mousedItem()->id > 0) {
 | 
						|
				_menu->addAction(lang(lng_context_select_msg), _widget, SLOT(selectMessage()))->setEnabled(true);
 | 
						|
				item = App::mousedItem();
 | 
						|
			}
 | 
						|
		}
 | 
						|
		App::contextItem(item);
 | 
						|
	}
 | 
						|
 | 
						|
	if (_menu->actions().isEmpty()) {
 | 
						|
		delete _menu;
 | 
						|
		_menu = 0;
 | 
						|
	} else {
 | 
						|
		connect(_menu, SIGNAL(destroyed(QObject*)), this, SLOT(onMenuDestroy(QObject*)));
 | 
						|
		_menu->popup(e->globalPos());
 | 
						|
		e->accept();
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryInner::onMenuDestroy(QObject *obj) {
 | 
						|
	if (_menu == obj) {
 | 
						|
		_menu = nullptr;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryInner::copySelectedText() {
 | 
						|
	setToClipboard(getSelectedText());
 | 
						|
}
 | 
						|
 | 
						|
void HistoryInner::copyContextUrl() {
 | 
						|
	if (_contextMenuLnk) {
 | 
						|
		_contextMenuLnk->copyToClipboard();
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryInner::saveContextImage() {
 | 
						|
    PhotoClickHandler *lnk = dynamic_cast<PhotoClickHandler*>(_contextMenuLnk.data());
 | 
						|
	if (!lnk) return;
 | 
						|
 | 
						|
	PhotoData *photo = lnk->photo();
 | 
						|
	if (!photo || !photo->date || !photo->loaded()) return;
 | 
						|
 | 
						|
	QString file;
 | 
						|
	auto filter = qsl("JPEG Image (*.jpg);;") + filedialogAllFilesFilter();
 | 
						|
	if (filedialogGetSaveFile(file, lang(lng_save_photo), filter, filedialogDefaultName(qsl("photo"), qsl(".jpg")))) {
 | 
						|
		if (!file.isEmpty()) {
 | 
						|
			photo->full->pix().toImage().save(file, "JPG");
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryInner::copyContextImage() {
 | 
						|
	PhotoClickHandler *lnk = dynamic_cast<PhotoClickHandler*>(_contextMenuLnk.data());
 | 
						|
	if (!lnk) return;
 | 
						|
 | 
						|
	PhotoData *photo = lnk->photo();
 | 
						|
	if (!photo || !photo->date || !photo->loaded()) return;
 | 
						|
 | 
						|
	QApplication::clipboard()->setPixmap(photo->full->pix());
 | 
						|
}
 | 
						|
 | 
						|
void HistoryInner::cancelContextDownload() {
 | 
						|
	if (DocumentClickHandler *lnkDocument = dynamic_cast<DocumentClickHandler*>(_contextMenuLnk.data())) {
 | 
						|
		lnkDocument->document()->cancel();
 | 
						|
	} else if (HistoryItem *item = App::contextItem()) {
 | 
						|
		if (HistoryMedia *media = item->getMedia()) {
 | 
						|
			if (DocumentData *doc = media->getDocument()) {
 | 
						|
				doc->cancel();
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryInner::showContextInFolder() {
 | 
						|
	QString filepath;
 | 
						|
	if (DocumentClickHandler *lnkDocument = dynamic_cast<DocumentClickHandler*>(_contextMenuLnk.data())) {
 | 
						|
		filepath = lnkDocument->document()->filepath(DocumentData::FilePathResolveChecked);
 | 
						|
	} else if (HistoryItem *item = App::contextItem()) {
 | 
						|
		if (HistoryMedia *media = item->getMedia()) {
 | 
						|
			if (DocumentData *doc = media->getDocument()) {
 | 
						|
				filepath = doc->filepath(DocumentData::FilePathResolveChecked);
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if (!filepath.isEmpty()) {
 | 
						|
		psShowInFolder(filepath);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryInner::saveContextFile() {
 | 
						|
	if (DocumentClickHandler *lnkDocument = dynamic_cast<DocumentClickHandler*>(_contextMenuLnk.data())) {
 | 
						|
		DocumentSaveClickHandler::doSave(lnkDocument->document(), true);
 | 
						|
	} else if (HistoryItem *item = App::contextItem()) {
 | 
						|
		if (HistoryMedia *media = item->getMedia()) {
 | 
						|
			if (DocumentData *doc = media->getDocument()) {
 | 
						|
				DocumentSaveClickHandler::doSave(doc, true);
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryInner::saveContextGif() {
 | 
						|
	if (HistoryItem *item = App::contextItem()) {
 | 
						|
		if (HistoryMedia *media = item->getMedia()) {
 | 
						|
			if (DocumentData *doc = media->getDocument()) {
 | 
						|
				_widget->saveGif(doc);
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryInner::copyContextText() {
 | 
						|
	HistoryItem *item = App::contextItem();
 | 
						|
	if (!item || (item->getMedia() && item->getMedia()->type() == MediaTypeSticker)) {
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	setToClipboard(item->selectedText(FullSelection));
 | 
						|
}
 | 
						|
 | 
						|
void HistoryInner::setToClipboard(const TextWithEntities &forClipboard, QClipboard::Mode mode) {
 | 
						|
	if (auto data = mimeDataFromTextWithEntities(forClipboard)) {
 | 
						|
		QApplication::clipboard()->setMimeData(data, mode);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryInner::resizeEvent(QResizeEvent *e) {
 | 
						|
	onUpdateSelected();
 | 
						|
}
 | 
						|
 | 
						|
TextWithEntities HistoryInner::getSelectedText() const {
 | 
						|
	SelectedItems sel = _selected;
 | 
						|
 | 
						|
	if (_dragAction == Selecting && _dragSelFrom && _dragSelTo) {
 | 
						|
		applyDragSelection(&sel);
 | 
						|
	}
 | 
						|
 | 
						|
	if (sel.isEmpty()) {
 | 
						|
		return TextWithEntities();
 | 
						|
	}
 | 
						|
	if (sel.cbegin().value() != FullSelection) {
 | 
						|
		return sel.cbegin().key()->selectedText(sel.cbegin().value());
 | 
						|
	}
 | 
						|
 | 
						|
	int fullSize = 0;
 | 
						|
	QString timeFormat(qsl(", [dd.MM.yy hh:mm]\n"));
 | 
						|
	QMap<int, TextWithEntities> texts;
 | 
						|
	for (auto i = sel.cbegin(), e = sel.cend(); i != e; ++i) {
 | 
						|
		HistoryItem *item = i.key();
 | 
						|
		if (item->detached()) continue;
 | 
						|
 | 
						|
		QString time = item->date.toString(timeFormat);
 | 
						|
		TextWithEntities part, unwrapped = item->selectedText(FullSelection);
 | 
						|
		int size = item->author()->name.size() + time.size() + unwrapped.text.size();
 | 
						|
		part.text.reserve(size);
 | 
						|
 | 
						|
		int y = itemTop(item);
 | 
						|
		if (y >= 0) {
 | 
						|
			part.text.append(item->author()->name).append(time);
 | 
						|
			appendTextWithEntities(part, std_::move(unwrapped));
 | 
						|
			texts.insert(y, part);
 | 
						|
			fullSize += size;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	TextWithEntities result;
 | 
						|
	auto sep = qsl("\n\n");
 | 
						|
	result.text.reserve(fullSize + (texts.size() - 1) * sep.size());
 | 
						|
	for (auto i = texts.begin(), e = texts.end(); i != e; ++i) {
 | 
						|
		appendTextWithEntities(result, std_::move(i.value()));
 | 
						|
		if (i + 1 != e) {
 | 
						|
			result.text.append(sep);
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return result;
 | 
						|
}
 | 
						|
 | 
						|
void HistoryInner::keyPressEvent(QKeyEvent *e) {
 | 
						|
	if (e->key() == Qt::Key_Escape) {
 | 
						|
		_widget->onListEscapePressed();
 | 
						|
	} else if (e == QKeySequence::Copy && !_selected.isEmpty()) {
 | 
						|
		copySelectedText();
 | 
						|
#ifdef Q_OS_MAC
 | 
						|
	} else if (e->key() == Qt::Key_E && e->modifiers().testFlag(Qt::ControlModifier)) {
 | 
						|
		setToClipboard(getSelectedText(), QClipboard::FindBuffer);
 | 
						|
#endif // Q_OS_MAC
 | 
						|
	} else if (e == QKeySequence::Delete) {
 | 
						|
		int32 selectedForForward, selectedForDelete;
 | 
						|
		getSelectionState(selectedForForward, selectedForDelete);
 | 
						|
		if (!_selected.isEmpty() && selectedForDelete == selectedForForward) {
 | 
						|
			_widget->onDeleteSelected();
 | 
						|
		}
 | 
						|
	} else {
 | 
						|
		e->ignore();
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryInner::recountHeight() {
 | 
						|
	int visibleHeight = _scroll->height();
 | 
						|
	int oldHistoryPaddingTop = qMax(visibleHeight - historyHeight() - st::historyPaddingBottom, 0);
 | 
						|
	if (_botAbout && !_botAbout->info->text.isEmpty()) {
 | 
						|
		accumulate_max(oldHistoryPaddingTop, st::msgMargin.top() + st::msgMargin.bottom() + st::msgPadding.top() + st::msgPadding.bottom() + st::msgNameFont->height + st::botDescSkip + _botAbout->height);
 | 
						|
	}
 | 
						|
 | 
						|
	_history->resizeGetHeight(_scroll->width());
 | 
						|
	if (_migrated) {
 | 
						|
		_migrated->resizeGetHeight(_scroll->width());
 | 
						|
	}
 | 
						|
 | 
						|
	// with migrated history we perhaps do not need to display first _history message
 | 
						|
	// (if last _migrated message and first _history message are both isGroupMigrate)
 | 
						|
	// or at least we don't need to display first _history date (just skip it by height)
 | 
						|
	_historySkipHeight = 0;
 | 
						|
	if (_migrated) {
 | 
						|
		if (!_migrated->isEmpty() && !_history->isEmpty() && _migrated->loadedAtBottom() && _history->loadedAtTop()) {
 | 
						|
			if (_migrated->blocks.back()->items.back()->date.date() == _history->blocks.front()->items.front()->date.date()) {
 | 
						|
				if (_migrated->blocks.back()->items.back()->isGroupMigrate() && _history->blocks.front()->items.front()->isGroupMigrate()) {
 | 
						|
					_historySkipHeight += _history->blocks.front()->items.front()->height();
 | 
						|
				} else {
 | 
						|
					_historySkipHeight += _history->blocks.front()->items.front()->displayedDateHeight();
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	updateBotInfo(false);
 | 
						|
	if (_botAbout && !_botAbout->info->text.isEmpty()) {
 | 
						|
		int32 tw = _scroll->width() - st::msgMargin.left() - st::msgMargin.right();
 | 
						|
		if (tw > st::msgMaxWidth) tw = st::msgMaxWidth;
 | 
						|
		tw -= st::msgPadding.left() + st::msgPadding.right();
 | 
						|
		int32 mw = qMax(_botAbout->info->text.maxWidth(), st::msgNameFont->width(lang(lng_bot_description)));
 | 
						|
		if (tw > mw) tw = mw;
 | 
						|
 | 
						|
		_botAbout->width = tw;
 | 
						|
		_botAbout->height = _botAbout->info->text.countHeight(_botAbout->width);
 | 
						|
 | 
						|
		int32 descH = st::msgMargin.top() + st::msgPadding.top() + st::msgNameFont->height + st::botDescSkip + _botAbout->height + st::msgPadding.bottom() + st::msgMargin.bottom();
 | 
						|
		int32 descMaxWidth = _scroll->width();
 | 
						|
		if (Adaptive::Wide()) {
 | 
						|
			descMaxWidth = qMin(descMaxWidth, int32(st::msgMaxWidth + 2 * st::msgPhotoSkip + 2 * st::msgMargin.left()));
 | 
						|
		}
 | 
						|
		int32 descAtX = (descMaxWidth - _botAbout->width) / 2 - st::msgPadding.left();
 | 
						|
		int32 descAtY = qMin(_historyPaddingTop - descH, qMax(0, (_scroll->height() - descH) / 2)) + st::msgMargin.top();
 | 
						|
 | 
						|
		_botAbout->rect = QRect(descAtX, descAtY, _botAbout->width + st::msgPadding.left() + st::msgPadding.right(), descH - st::msgMargin.top() - st::msgMargin.bottom());
 | 
						|
	} else if (_botAbout) {
 | 
						|
		_botAbout->width = _botAbout->height = 0;
 | 
						|
		_botAbout->rect = QRect();
 | 
						|
	}
 | 
						|
 | 
						|
	int newHistoryPaddingTop = qMax(visibleHeight - historyHeight() - st::historyPaddingBottom, 0);
 | 
						|
	if (_botAbout && !_botAbout->info->text.isEmpty()) {
 | 
						|
		accumulate_max(newHistoryPaddingTop, st::msgMargin.top() + st::msgMargin.bottom() + st::msgPadding.top() + st::msgPadding.bottom() + st::msgNameFont->height + st::botDescSkip + _botAbout->height);
 | 
						|
	}
 | 
						|
 | 
						|
	auto historyPaddingTopDelta = (newHistoryPaddingTop - oldHistoryPaddingTop);
 | 
						|
	if (historyPaddingTopDelta != 0) {
 | 
						|
		if (_history->scrollTopItem) {
 | 
						|
			_history->scrollTopOffset += historyPaddingTopDelta;
 | 
						|
		} else if (_migrated && _migrated->scrollTopItem) {
 | 
						|
			_migrated->scrollTopOffset += historyPaddingTopDelta;
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryInner::updateBotInfo(bool recount) {
 | 
						|
	int newh = 0;
 | 
						|
	if (_botAbout && !_botAbout->info->description.isEmpty()) {
 | 
						|
		if (_botAbout->info->text.isEmpty()) {
 | 
						|
			_botAbout->info->text.setText(st::msgFont, _botAbout->info->description, _historyBotNoMonoOptions);
 | 
						|
			if (recount) {
 | 
						|
				int32 tw = _scroll->width() - st::msgMargin.left() - st::msgMargin.right();
 | 
						|
				if (tw > st::msgMaxWidth) tw = st::msgMaxWidth;
 | 
						|
				tw -= st::msgPadding.left() + st::msgPadding.right();
 | 
						|
				int32 mw = qMax(_botAbout->info->text.maxWidth(), st::msgNameFont->width(lang(lng_bot_description)));
 | 
						|
				if (tw > mw) tw = mw;
 | 
						|
 | 
						|
				_botAbout->width = tw;
 | 
						|
				newh = _botAbout->info->text.countHeight(_botAbout->width);
 | 
						|
			}
 | 
						|
		} else if (recount) {
 | 
						|
			newh = _botAbout->height;
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if (recount && _botAbout) {
 | 
						|
		if (_botAbout->height != newh) {
 | 
						|
			_botAbout->height = newh;
 | 
						|
			updateSize();
 | 
						|
		}
 | 
						|
		if (_botAbout->height > 0) {
 | 
						|
			int32 descH = st::msgMargin.top() + st::msgPadding.top() + st::msgNameFont->height + st::botDescSkip + _botAbout->height + st::msgPadding.bottom() + st::msgMargin.bottom();
 | 
						|
			int32 descAtX = (_scroll->width() - _botAbout->width) / 2 - st::msgPadding.left();
 | 
						|
			int32 descAtY = qMin(_historyPaddingTop - descH, (_scroll->height() - descH) / 2) + st::msgMargin.top();
 | 
						|
 | 
						|
			_botAbout->rect = QRect(descAtX, descAtY, _botAbout->width + st::msgPadding.left() + st::msgPadding.right(), descH - st::msgMargin.top() - st::msgMargin.bottom());
 | 
						|
		} else {
 | 
						|
			_botAbout->width = 0;
 | 
						|
			_botAbout->rect = QRect();
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
bool HistoryInner::wasSelectedText() const {
 | 
						|
	return _wasSelectedText;
 | 
						|
}
 | 
						|
 | 
						|
void HistoryInner::setFirstLoading(bool loading) {
 | 
						|
	_firstLoading = loading;
 | 
						|
	update();
 | 
						|
}
 | 
						|
 | 
						|
void HistoryInner::visibleAreaUpdated(int top, int bottom) {
 | 
						|
	_visibleAreaTop = top;
 | 
						|
	_visibleAreaBottom = bottom;
 | 
						|
 | 
						|
	// if history has pending resize events we should not update scrollTopItem
 | 
						|
	if (hasPendingResizedItems()) {
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	if (bottom >= _historyPaddingTop + historyHeight() + st::historyPaddingBottom) {
 | 
						|
		_history->forgetScrollState();
 | 
						|
		if (_migrated) {
 | 
						|
			_migrated->forgetScrollState();
 | 
						|
		}
 | 
						|
	} else {
 | 
						|
		int htop = historyTop(), mtop = migratedTop();
 | 
						|
		if ((htop >= 0 && top >= htop) || mtop < 0) {
 | 
						|
			_history->countScrollState(top - htop);
 | 
						|
			if (_migrated) {
 | 
						|
				_migrated->forgetScrollState();
 | 
						|
			}
 | 
						|
		} else if (mtop >= 0 && top >= mtop) {
 | 
						|
			_history->forgetScrollState();
 | 
						|
			_migrated->countScrollState(top - mtop);
 | 
						|
		} else {
 | 
						|
			_history->countScrollState(top - htop);
 | 
						|
			if (_migrated) {
 | 
						|
				_migrated->forgetScrollState();
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	_scrollDateCheck.call();
 | 
						|
}
 | 
						|
 | 
						|
bool HistoryInner::displayScrollDate() const{
 | 
						|
	return (_visibleAreaTop <= height() - 2 * (_visibleAreaBottom - _visibleAreaTop));
 | 
						|
}
 | 
						|
 | 
						|
void HistoryInner::onScrollDateCheck() {
 | 
						|
	if (!_history) return;
 | 
						|
 | 
						|
	auto newScrollDateItem = _history->scrollTopItem ? _history->scrollTopItem : (_migrated ? _migrated->scrollTopItem : nullptr);
 | 
						|
	auto newScrollDateItemTop = _history->scrollTopItem ? _history->scrollTopOffset : (_migrated ? _migrated->scrollTopOffset : 0);
 | 
						|
	//if (newScrollDateItem && !displayScrollDate()) {
 | 
						|
	//	if (!_history->isEmpty() && newScrollDateItem->date.date() == _history->blocks.back()->items.back()->date.date()) {
 | 
						|
	//		newScrollDateItem = nullptr;
 | 
						|
	//	}
 | 
						|
	//}
 | 
						|
	if (!newScrollDateItem) {
 | 
						|
		_scrollDateLastItem = nullptr;
 | 
						|
		_scrollDateLastItemTop = 0;
 | 
						|
		onScrollDateHide();
 | 
						|
	} else if (newScrollDateItem != _scrollDateLastItem || newScrollDateItemTop != _scrollDateLastItemTop) {
 | 
						|
		// Show scroll date only if it is not the initial onScroll() event (with empty _scrollDateLastItem).
 | 
						|
		if (_scrollDateLastItem && !_scrollDateShown) {
 | 
						|
			toggleScrollDateShown();
 | 
						|
		}
 | 
						|
		_scrollDateLastItem = newScrollDateItem;
 | 
						|
		_scrollDateLastItemTop = newScrollDateItemTop;
 | 
						|
		_scrollDateHideTimer.start(ScrollDateHideTimeout);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryInner::onScrollDateHide() {
 | 
						|
	_scrollDateHideTimer.stop();
 | 
						|
	if (_scrollDateShown) {
 | 
						|
		toggleScrollDateShown();
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryInner::toggleScrollDateShown() {
 | 
						|
	_scrollDateShown = !_scrollDateShown;
 | 
						|
	auto from = _scrollDateShown ? 0. : 1.;
 | 
						|
	auto to = _scrollDateShown ? 1. : 0.;
 | 
						|
	_scrollDateOpacity.start([this] { repaintScrollDateCallback(); }, from, to, st::btnAttachEmoji.duration);
 | 
						|
}
 | 
						|
 | 
						|
void HistoryInner::repaintScrollDateCallback() {
 | 
						|
	int updateTop = _visibleAreaTop;
 | 
						|
	int updateHeight = st::msgServiceMargin.top() + st::msgServicePadding.top() + st::msgServiceFont->height + st::msgServicePadding.bottom();
 | 
						|
	update(0, updateTop, width(), updateHeight);
 | 
						|
}
 | 
						|
 | 
						|
void HistoryInner::updateSize() {
 | 
						|
	int visibleHeight = _scroll->height();
 | 
						|
	int newHistoryPaddingTop = qMax(visibleHeight - historyHeight() - st::historyPaddingBottom, 0);
 | 
						|
	if (_botAbout && !_botAbout->info->text.isEmpty()) {
 | 
						|
		accumulate_max(newHistoryPaddingTop, st::msgMargin.top() + st::msgMargin.bottom() + st::msgPadding.top() + st::msgPadding.bottom() + st::msgNameFont->height + st::botDescSkip + _botAbout->height);
 | 
						|
	}
 | 
						|
 | 
						|
	if (_botAbout && _botAbout->height > 0) {
 | 
						|
		int32 descH = st::msgMargin.top() + st::msgPadding.top() + st::msgNameFont->height + st::botDescSkip + _botAbout->height + st::msgPadding.bottom() + st::msgMargin.bottom();
 | 
						|
		int32 descMaxWidth = _scroll->width();
 | 
						|
		if (Adaptive::Wide()) {
 | 
						|
			descMaxWidth = qMin(descMaxWidth, int32(st::msgMaxWidth + 2 * st::msgPhotoSkip + 2 * st::msgMargin.left()));
 | 
						|
		}
 | 
						|
		int32 descAtX = (descMaxWidth - _botAbout->width) / 2 - st::msgPadding.left();
 | 
						|
		int32 descAtY = qMin(newHistoryPaddingTop - descH, qMax(0, (_scroll->height() - descH) / 2)) + st::msgMargin.top();
 | 
						|
 | 
						|
		_botAbout->rect = QRect(descAtX, descAtY, _botAbout->width + st::msgPadding.left() + st::msgPadding.right(), descH - st::msgMargin.top() - st::msgMargin.bottom());
 | 
						|
	}
 | 
						|
 | 
						|
	_historyPaddingTop = newHistoryPaddingTop;
 | 
						|
 | 
						|
	int newHeight = _historyPaddingTop + historyHeight() + st::historyPaddingBottom;
 | 
						|
	if (width() != _scroll->width() || height() != newHeight) {
 | 
						|
		resize(_scroll->width(), newHeight);
 | 
						|
 | 
						|
		dragActionUpdate(QCursor::pos());
 | 
						|
	} else {
 | 
						|
		update();
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryInner::enterEvent(QEvent *e) {
 | 
						|
	dragActionUpdate(QCursor::pos());
 | 
						|
//	return QWidget::enterEvent(e);
 | 
						|
}
 | 
						|
 | 
						|
void HistoryInner::leaveEvent(QEvent *e) {
 | 
						|
	if (HistoryItem *item = App::hoveredItem()) {
 | 
						|
		repaintItem(item);
 | 
						|
		App::hoveredItem(nullptr);
 | 
						|
	}
 | 
						|
	ClickHandler::clearActive();
 | 
						|
	PopupTooltip::Hide();
 | 
						|
	if (!ClickHandler::getPressed() && _cursor != style::cur_default) {
 | 
						|
		_cursor = style::cur_default;
 | 
						|
		setCursor(_cursor);
 | 
						|
	}
 | 
						|
	return QWidget::leaveEvent(e);
 | 
						|
}
 | 
						|
 | 
						|
HistoryInner::~HistoryInner() {
 | 
						|
	delete _menu;
 | 
						|
	_dragAction = NoDrag;
 | 
						|
}
 | 
						|
 | 
						|
bool HistoryInner::focusNextPrevChild(bool next) {
 | 
						|
	if (_selected.isEmpty()) {
 | 
						|
		return TWidget::focusNextPrevChild(next);
 | 
						|
	} else {
 | 
						|
		clearSelectedItems();
 | 
						|
		return true;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryInner::adjustCurrent(int32 y) const {
 | 
						|
	int32 htop = historyTop(), hdrawtop = historyDrawTop(), mtop = migratedTop();
 | 
						|
	_curHistory = 0;
 | 
						|
	if (mtop >= 0) {
 | 
						|
		adjustCurrent(y - mtop, _migrated);
 | 
						|
	}
 | 
						|
	if (htop >= 0 && hdrawtop >= 0 && (mtop < 0 || y >= hdrawtop)) {
 | 
						|
		adjustCurrent(y - htop, _history);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryInner::adjustCurrent(int32 y, History *history) const {
 | 
						|
	t_assert(!history->isEmpty());
 | 
						|
	_curHistory = history;
 | 
						|
	if (_curBlock >= history->blocks.size()) {
 | 
						|
		_curBlock = history->blocks.size() - 1;
 | 
						|
		_curItem = 0;
 | 
						|
	}
 | 
						|
	while (history->blocks.at(_curBlock)->y > y && _curBlock > 0) {
 | 
						|
		--_curBlock;
 | 
						|
		_curItem = 0;
 | 
						|
	}
 | 
						|
	while (history->blocks.at(_curBlock)->y + history->blocks.at(_curBlock)->height <= y && _curBlock + 1 < history->blocks.size()) {
 | 
						|
		++_curBlock;
 | 
						|
		_curItem = 0;
 | 
						|
	}
 | 
						|
	HistoryBlock *block = history->blocks.at(_curBlock);
 | 
						|
	if (_curItem >= block->items.size()) {
 | 
						|
		_curItem = block->items.size() - 1;
 | 
						|
	}
 | 
						|
	int by = block->y;
 | 
						|
	while (block->items.at(_curItem)->y + by > y && _curItem > 0) {
 | 
						|
		--_curItem;
 | 
						|
	}
 | 
						|
	while (block->items.at(_curItem)->y + block->items.at(_curItem)->height() + by <= y && _curItem + 1 < block->items.size()) {
 | 
						|
		++_curItem;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
HistoryItem *HistoryInner::prevItem(HistoryItem *item) {
 | 
						|
	if (!item || item->detached()) return nullptr;
 | 
						|
 | 
						|
	HistoryBlock *block = item->block();
 | 
						|
	int blockIndex = block->indexInHistory(), itemIndex = item->indexInBlock();
 | 
						|
	if (itemIndex > 0) {
 | 
						|
		return block->items.at(itemIndex - 1);
 | 
						|
	}
 | 
						|
	if (blockIndex > 0) {
 | 
						|
		return item->history()->blocks.at(blockIndex - 1)->items.back();
 | 
						|
	}
 | 
						|
	if (item->history() == _history && _migrated && _history->loadedAtTop() && !_migrated->isEmpty() && _migrated->loadedAtBottom()) {
 | 
						|
		return _migrated->blocks.back()->items.back();
 | 
						|
	}
 | 
						|
	return nullptr;
 | 
						|
}
 | 
						|
 | 
						|
HistoryItem *HistoryInner::nextItem(HistoryItem *item) {
 | 
						|
	if (!item || item->detached()) return nullptr;
 | 
						|
 | 
						|
	HistoryBlock *block = item->block();
 | 
						|
	int blockIndex = block->indexInHistory(), itemIndex = item->indexInBlock();
 | 
						|
	if (itemIndex + 1 < block->items.size()) {
 | 
						|
		return block->items.at(itemIndex + 1);
 | 
						|
	}
 | 
						|
	if (blockIndex + 1 < item->history()->blocks.size()) {
 | 
						|
		return item->history()->blocks.at(blockIndex + 1)->items.front();
 | 
						|
	}
 | 
						|
	if (item->history() == _migrated && _history && _migrated->loadedAtBottom() && _history->loadedAtTop() && !_history->isEmpty()) {
 | 
						|
		return _history->blocks.front()->items.front();
 | 
						|
	}
 | 
						|
	return nullptr;
 | 
						|
}
 | 
						|
 | 
						|
bool HistoryInner::canCopySelected() const {
 | 
						|
	return !_selected.isEmpty();
 | 
						|
}
 | 
						|
 | 
						|
bool HistoryInner::canDeleteSelected() const {
 | 
						|
	if (_selected.isEmpty() || _selected.cbegin().value() != FullSelection) return false;
 | 
						|
	int32 selectedForForward, selectedForDelete;
 | 
						|
	getSelectionState(selectedForForward, selectedForDelete);
 | 
						|
	return (selectedForForward == selectedForDelete);
 | 
						|
}
 | 
						|
 | 
						|
void HistoryInner::getSelectionState(int32 &selectedForForward, int32 &selectedForDelete) const {
 | 
						|
	selectedForForward = selectedForDelete = 0;
 | 
						|
	for (SelectedItems::const_iterator i = _selected.cbegin(), e = _selected.cend(); i != e; ++i) {
 | 
						|
		if (i.key()->type() == HistoryItemMsg && i.value() == FullSelection) {
 | 
						|
			if (i.key()->canDelete()) {
 | 
						|
				++selectedForDelete;
 | 
						|
			}
 | 
						|
			++selectedForForward;
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if (!selectedForDelete && !selectedForForward && !_selected.isEmpty()) { // text selection
 | 
						|
		selectedForForward = -1;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryInner::clearSelectedItems(bool onlyTextSelection) {
 | 
						|
	if (!_selected.isEmpty() && (!onlyTextSelection || _selected.cbegin().value() != FullSelection)) {
 | 
						|
		_selected.clear();
 | 
						|
		_widget->updateTopBarSelection();
 | 
						|
		_widget->update();
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryInner::fillSelectedItems(SelectedItemSet &sel, bool forDelete) {
 | 
						|
	if (_selected.isEmpty() || _selected.cbegin().value() != FullSelection) return;
 | 
						|
 | 
						|
	for (SelectedItems::const_iterator i = _selected.cbegin(), e = _selected.cend(); i != e; ++i) {
 | 
						|
		HistoryItem *item = i.key();
 | 
						|
		if (item && item->toHistoryMessage() && item->id > 0) {
 | 
						|
			if (item->history() == _migrated) {
 | 
						|
				sel.insert(item->id - ServerMaxMsgId, item);
 | 
						|
			} else {
 | 
						|
				sel.insert(item->id, item);
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryInner::selectItem(HistoryItem *item) {
 | 
						|
	if (!_selected.isEmpty() && _selected.cbegin().value() != FullSelection) {
 | 
						|
		_selected.clear();
 | 
						|
	} else if (_selected.size() == MaxSelectedItems && _selected.constFind(item) == _selected.cend()) {
 | 
						|
		return;
 | 
						|
	}
 | 
						|
	_selected.insert(item, FullSelection);
 | 
						|
	_widget->updateTopBarSelection();
 | 
						|
	_widget->update();
 | 
						|
}
 | 
						|
 | 
						|
void HistoryInner::onTouchSelect() {
 | 
						|
	_touchSelect = true;
 | 
						|
	dragActionStart(_touchPos);
 | 
						|
}
 | 
						|
 | 
						|
void HistoryInner::onUpdateSelected() {
 | 
						|
	if (!_history || hasPendingResizedItems()) {
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	QPoint mousePos(mapFromGlobal(_dragPos));
 | 
						|
	QPoint point(_widget->clampMousePosition(mousePos));
 | 
						|
 | 
						|
	HistoryBlock *block = 0;
 | 
						|
	HistoryItem *item = 0;
 | 
						|
	QPoint m;
 | 
						|
 | 
						|
	adjustCurrent(point.y());
 | 
						|
	if (_curHistory && !_curHistory->isEmpty()) {
 | 
						|
		block = _curHistory->blocks[_curBlock];
 | 
						|
		item = block->items[_curItem];
 | 
						|
 | 
						|
		App::mousedItem(item);
 | 
						|
		m = mapMouseToItem(point, item);
 | 
						|
		if (item->hasPoint(m.x(), m.y())) {
 | 
						|
			if (App::hoveredItem() != item) {
 | 
						|
				repaintItem(App::hoveredItem());
 | 
						|
				App::hoveredItem(item);
 | 
						|
				repaintItem(App::hoveredItem());
 | 
						|
			}
 | 
						|
		} else if (App::hoveredItem()) {
 | 
						|
			repaintItem(App::hoveredItem());
 | 
						|
			App::hoveredItem(0);
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if (_dragItem && _dragItem->detached()) {
 | 
						|
		dragActionCancel();
 | 
						|
	}
 | 
						|
 | 
						|
	HistoryTextState dragState;
 | 
						|
	ClickHandlerHost *lnkhost = nullptr;
 | 
						|
	bool selectingText = (item == _dragItem && item == App::hoveredItem() && !_selected.isEmpty() && _selected.cbegin().value() != FullSelection);
 | 
						|
	if (point.y() < _historyPaddingTop) {
 | 
						|
		if (_botAbout && !_botAbout->info->text.isEmpty() && _botAbout->height > 0) {
 | 
						|
			dragState = _botAbout->info->text.getState(point.x() - _botAbout->rect.left() - st::msgPadding.left(), point.y() - _botAbout->rect.top() - st::msgPadding.top() - st::botDescSkip - st::msgNameFont->height, _botAbout->width);
 | 
						|
			lnkhost = _botAbout.get();
 | 
						|
		}
 | 
						|
	} else if (item) {
 | 
						|
		if (item != _dragItem || (m - _dragStartPos).manhattanLength() >= QApplication::startDragDistance()) {
 | 
						|
			if (_dragAction == PrepareDrag) {
 | 
						|
				_dragAction = Dragging;
 | 
						|
				QTimer::singleShot(1, this, SLOT(onDragExec()));
 | 
						|
			} else if (_dragAction == PrepareSelect) {
 | 
						|
				_dragAction = Selecting;
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		HistoryStateRequest request;
 | 
						|
		if (_dragAction == Selecting) {
 | 
						|
			request.flags |= Text::StateRequest::Flag::LookupSymbol;
 | 
						|
		} else {
 | 
						|
			selectingText = false;
 | 
						|
		}
 | 
						|
		dragState = item->getState(m.x(), m.y(), request);
 | 
						|
		lnkhost = item;
 | 
						|
		if (!dragState.link && m.x() >= st::msgMargin.left() && m.x() < st::msgMargin.left() + st::msgPhotoSize) {
 | 
						|
			if (HistoryMessage *msg = item->toHistoryMessage()) {
 | 
						|
				if (msg->hasFromPhoto()) {
 | 
						|
					enumerateUserpics([&dragState, &lnkhost, &point](HistoryMessage *message, int userpicTop) -> bool {
 | 
						|
						// stop enumeration if the userpic is above our point
 | 
						|
						if (userpicTop + st::msgPhotoSize <= point.y()) {
 | 
						|
							return false;
 | 
						|
						}
 | 
						|
 | 
						|
						// stop enumeration if we've found a userpic under the cursor
 | 
						|
						if (point.y() >= userpicTop && point.y() < userpicTop + st::msgPhotoSize) {
 | 
						|
							dragState.link = message->from()->openLink();
 | 
						|
							lnkhost = message;
 | 
						|
							return false;
 | 
						|
						}
 | 
						|
						return true;
 | 
						|
					});
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	bool lnkChanged = ClickHandler::setActive(dragState.link, lnkhost);
 | 
						|
	if (lnkChanged || dragState.cursor != _dragCursorState) {
 | 
						|
		PopupTooltip::Hide();
 | 
						|
	}
 | 
						|
	if (dragState.link || dragState.cursor == HistoryInDateCursorState || dragState.cursor == HistoryInForwardedCursorState) {
 | 
						|
		PopupTooltip::Show(1000, this);
 | 
						|
	}
 | 
						|
 | 
						|
	Qt::CursorShape cur = style::cur_default;
 | 
						|
	if (_dragAction == NoDrag) {
 | 
						|
		_dragCursorState = dragState.cursor;
 | 
						|
		if (dragState.link) {
 | 
						|
			cur = style::cur_pointer;
 | 
						|
		} else if (_dragCursorState == HistoryInTextCursorState && (_selected.isEmpty() || _selected.cbegin().value() != FullSelection)) {
 | 
						|
			cur = style::cur_text;
 | 
						|
		} else if (_dragCursorState == HistoryInDateCursorState) {
 | 
						|
//			cur = style::cur_cross;
 | 
						|
		}
 | 
						|
	} else if (item) {
 | 
						|
		if (_dragAction == Selecting) {
 | 
						|
			bool canSelectMany = (_history != nullptr);
 | 
						|
			if (selectingText) {
 | 
						|
				uint16 second = dragState.symbol;
 | 
						|
				if (dragState.afterSymbol && _dragSelType == TextSelectType::Letters) {
 | 
						|
					++second;
 | 
						|
				}
 | 
						|
				auto selState = _dragItem->adjustSelection({ qMin(second, _dragSymbol), qMax(second, _dragSymbol) }, _dragSelType);
 | 
						|
				if (_selected[_dragItem] != selState) {
 | 
						|
					_selected[_dragItem] = selState;
 | 
						|
					repaintItem(_dragItem);
 | 
						|
				}
 | 
						|
				if (!_wasSelectedText && (selState == FullSelection || selState.from != selState.to)) {
 | 
						|
					_wasSelectedText = true;
 | 
						|
					setFocus();
 | 
						|
				}
 | 
						|
				updateDragSelection(0, 0, false);
 | 
						|
			} else if (canSelectMany) {
 | 
						|
				bool selectingDown = (itemTop(_dragItem) < itemTop(item)) || (_dragItem == item && _dragStartPos.y() < m.y());
 | 
						|
				HistoryItem *dragSelFrom = _dragItem, *dragSelTo = item;
 | 
						|
				if (!dragSelFrom->hasPoint(_dragStartPos.x(), _dragStartPos.y())) { // maybe exclude dragSelFrom
 | 
						|
					if (selectingDown) {
 | 
						|
						if (_dragStartPos.y() >= dragSelFrom->height() - dragSelFrom->marginBottom() || ((item == dragSelFrom) && (m.y() < _dragStartPos.y() + QApplication::startDragDistance() || m.y() < dragSelFrom->marginTop()))) {
 | 
						|
							dragSelFrom = (dragSelFrom == dragSelTo) ? 0 : nextItem(dragSelFrom);
 | 
						|
						}
 | 
						|
					} else {
 | 
						|
						if (_dragStartPos.y() < dragSelFrom->marginTop() || ((item == dragSelFrom) && (m.y() >= _dragStartPos.y() - QApplication::startDragDistance() || m.y() >= dragSelFrom->height() - dragSelFrom->marginBottom()))) {
 | 
						|
							dragSelFrom = (dragSelFrom == dragSelTo) ? 0 : prevItem(dragSelFrom);
 | 
						|
						}
 | 
						|
					}
 | 
						|
				}
 | 
						|
				if (_dragItem != item) { // maybe exclude dragSelTo
 | 
						|
					if (selectingDown) {
 | 
						|
						if (m.y() < dragSelTo->marginTop()) {
 | 
						|
							dragSelTo = (dragSelFrom == dragSelTo) ? 0 : prevItem(dragSelTo);
 | 
						|
						}
 | 
						|
					} else {
 | 
						|
						if (m.y() >= dragSelTo->height() - dragSelTo->marginBottom()) {
 | 
						|
							dragSelTo = (dragSelFrom == dragSelTo) ? 0 : nextItem(dragSelTo);
 | 
						|
						}
 | 
						|
					}
 | 
						|
				}
 | 
						|
				bool dragSelecting = false;
 | 
						|
				HistoryItem *dragFirstAffected = dragSelFrom;
 | 
						|
				while (dragFirstAffected && (dragFirstAffected->id < 0 || dragFirstAffected->serviceMsg())) {
 | 
						|
					dragFirstAffected = (dragFirstAffected == dragSelTo) ? 0 : (selectingDown ? nextItem(dragFirstAffected) : prevItem(dragFirstAffected));
 | 
						|
				}
 | 
						|
				if (dragFirstAffected) {
 | 
						|
					SelectedItems::const_iterator i = _selected.constFind(dragFirstAffected);
 | 
						|
					dragSelecting = (i == _selected.cend() || i.value() != FullSelection);
 | 
						|
				}
 | 
						|
				updateDragSelection(dragSelFrom, dragSelTo, dragSelecting);
 | 
						|
			}
 | 
						|
		} else if (_dragAction == Dragging) {
 | 
						|
		}
 | 
						|
 | 
						|
		if (ClickHandler::getPressed()) {
 | 
						|
			cur = style::cur_pointer;
 | 
						|
		} else if (_dragAction == Selecting && !_selected.isEmpty() && _selected.cbegin().value() != FullSelection) {
 | 
						|
			if (!_dragSelFrom || !_dragSelTo) {
 | 
						|
				cur = style::cur_text;
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if (_dragAction == Selecting) {
 | 
						|
		_widget->checkSelectingScroll(mousePos);
 | 
						|
	} else {
 | 
						|
		updateDragSelection(0, 0, false);
 | 
						|
		_widget->noSelectingScroll();
 | 
						|
	}
 | 
						|
 | 
						|
	if (_dragAction == NoDrag && (lnkChanged || cur != _cursor)) {
 | 
						|
		setCursor(_cursor = cur);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryInner::updateDragSelection(HistoryItem *dragSelFrom, HistoryItem *dragSelTo, bool dragSelecting, bool force) {
 | 
						|
	if (_dragSelFrom != dragSelFrom || _dragSelTo != dragSelTo || _dragSelecting != dragSelecting) {
 | 
						|
		_dragSelFrom = dragSelFrom;
 | 
						|
		_dragSelTo = dragSelTo;
 | 
						|
		int32 fromy = itemTop(_dragSelFrom), toy = itemTop(_dragSelTo);
 | 
						|
		if (fromy >= 0 && toy >= 0 && fromy > toy) {
 | 
						|
			qSwap(_dragSelFrom, _dragSelTo);
 | 
						|
		}
 | 
						|
		_dragSelecting = dragSelecting;
 | 
						|
		if (!_wasSelectedText && _dragSelFrom && _dragSelTo && _dragSelecting) {
 | 
						|
			_wasSelectedText = true;
 | 
						|
			setFocus();
 | 
						|
		}
 | 
						|
		force = true;
 | 
						|
	}
 | 
						|
	if (!force) return;
 | 
						|
 | 
						|
	update();
 | 
						|
}
 | 
						|
 | 
						|
void HistoryInner::BotAbout::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) {
 | 
						|
	_parent->update(rect);
 | 
						|
}
 | 
						|
 | 
						|
void HistoryInner::BotAbout::clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) {
 | 
						|
	_parent->update(rect);
 | 
						|
}
 | 
						|
 | 
						|
int HistoryInner::historyHeight() const {
 | 
						|
	int result = 0;
 | 
						|
	if (!_history || _history->isEmpty()) {
 | 
						|
		result += _migrated ? _migrated->height : 0;
 | 
						|
	} else {
 | 
						|
		result += _history->height - _historySkipHeight + (_migrated ? _migrated->height : 0);
 | 
						|
	}
 | 
						|
	return result;
 | 
						|
}
 | 
						|
 | 
						|
int HistoryInner::historyScrollTop() const {
 | 
						|
	int htop = historyTop(), mtop = migratedTop();
 | 
						|
	if (htop >= 0 && _history->scrollTopItem) {
 | 
						|
		t_assert(!_history->scrollTopItem->detached());
 | 
						|
		return htop + _history->scrollTopItem->block()->y + _history->scrollTopItem->y + _history->scrollTopOffset;
 | 
						|
	}
 | 
						|
	if (mtop >= 0 && _migrated->scrollTopItem) {
 | 
						|
		t_assert(!_migrated->scrollTopItem->detached());
 | 
						|
		return mtop + _migrated->scrollTopItem->block()->y + _migrated->scrollTopItem->y + _migrated->scrollTopOffset;
 | 
						|
	}
 | 
						|
	return ScrollMax;
 | 
						|
}
 | 
						|
 | 
						|
int HistoryInner::migratedTop() const {
 | 
						|
	return (_migrated && !_migrated->isEmpty()) ? _historyPaddingTop : -1;
 | 
						|
}
 | 
						|
 | 
						|
int HistoryInner::historyTop() const {
 | 
						|
	int mig = migratedTop();
 | 
						|
	return (_history && !_history->isEmpty()) ? (mig >= 0 ? (mig + _migrated->height - _historySkipHeight) : _historyPaddingTop) : -1;
 | 
						|
}
 | 
						|
 | 
						|
int HistoryInner::historyDrawTop() const {
 | 
						|
	int his = historyTop();
 | 
						|
	return (his >= 0) ? (his + _historySkipHeight) : -1;
 | 
						|
}
 | 
						|
 | 
						|
int HistoryInner::itemTop(const HistoryItem *item) const { // -1 if should not be visible, -2 if bad history()
 | 
						|
	if (!item) return -2;
 | 
						|
	if (item->detached()) return -1;
 | 
						|
 | 
						|
	int top = (item->history() == _history) ? historyTop() : (item->history() == _migrated ? migratedTop() : -2);
 | 
						|
	return (top < 0) ? top : (top + item->y + item->block()->y);
 | 
						|
}
 | 
						|
 | 
						|
void HistoryInner::notifyIsBotChanged() {
 | 
						|
	BotInfo *newinfo = (_history && _history->peer->isUser()) ? _history->peer->asUser()->botInfo.get() : nullptr;
 | 
						|
	if ((!newinfo && !_botAbout) || (newinfo && _botAbout && _botAbout->info == newinfo)) {
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	if (newinfo) {
 | 
						|
		_botAbout.reset(new BotAbout(this, newinfo));
 | 
						|
		if (newinfo && !newinfo->inited && App::api()) {
 | 
						|
			App::api()->requestFullPeer(_peer);
 | 
						|
		}
 | 
						|
	} else {
 | 
						|
		_botAbout = nullptr;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryInner::notifyMigrateUpdated() {
 | 
						|
	_migrated = _peer->migrateFrom() ? App::history(_peer->migrateFrom()->id) : 0;
 | 
						|
}
 | 
						|
 | 
						|
int HistoryInner::moveScrollFollowingInlineKeyboard(const HistoryItem *item, int oldKeyboardTop, int newKeyboardTop) {
 | 
						|
	if (item == App::mousedItem()) {
 | 
						|
		int top = itemTop(item);
 | 
						|
		if (top >= oldKeyboardTop) {
 | 
						|
			return newKeyboardTop - oldKeyboardTop;
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
void HistoryInner::applyDragSelection() {
 | 
						|
	applyDragSelection(&_selected);
 | 
						|
}
 | 
						|
 | 
						|
void HistoryInner::addSelectionRange(SelectedItems *toItems, int32 fromblock, int32 fromitem, int32 toblock, int32 toitem, History *h) const {
 | 
						|
	if (fromblock >= 0 && fromitem >= 0 && toblock >= 0 && toitem >= 0) {
 | 
						|
		for (; fromblock <= toblock; ++fromblock) {
 | 
						|
			HistoryBlock *block = h->blocks[fromblock];
 | 
						|
			for (int32 cnt = (fromblock < toblock) ? block->items.size() : (toitem + 1); fromitem < cnt; ++fromitem) {
 | 
						|
				HistoryItem *item = block->items[fromitem];
 | 
						|
				SelectedItems::iterator i = toItems->find(item);
 | 
						|
				if (item->id > 0 && !item->serviceMsg()) {
 | 
						|
					if (i == toItems->cend()) {
 | 
						|
						if (toItems->size() >= MaxSelectedItems) break;
 | 
						|
						toItems->insert(item, FullSelection);
 | 
						|
					} else if (i.value() != FullSelection) {
 | 
						|
						*i = FullSelection;
 | 
						|
					}
 | 
						|
				} else {
 | 
						|
					if (i != toItems->cend()) {
 | 
						|
						toItems->erase(i);
 | 
						|
					}
 | 
						|
				}
 | 
						|
			}
 | 
						|
			if (toItems->size() >= MaxSelectedItems) break;
 | 
						|
			fromitem = 0;
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryInner::applyDragSelection(SelectedItems *toItems) const {
 | 
						|
	int32 selfromy = itemTop(_dragSelFrom), seltoy = itemTop(_dragSelTo);
 | 
						|
	if (selfromy < 0 || seltoy < 0) {
 | 
						|
		return;
 | 
						|
	}
 | 
						|
	seltoy += _dragSelTo->height();
 | 
						|
 | 
						|
	if (!toItems->isEmpty() && toItems->cbegin().value() != FullSelection) {
 | 
						|
		toItems->clear();
 | 
						|
	}
 | 
						|
	if (_dragSelecting) {
 | 
						|
		int32 fromblock = _dragSelFrom->block()->indexInHistory(), fromitem = _dragSelFrom->indexInBlock();
 | 
						|
		int32 toblock = _dragSelTo->block()->indexInHistory(), toitem = _dragSelTo->indexInBlock();
 | 
						|
		if (_migrated) {
 | 
						|
			if (_dragSelFrom->history() == _migrated) {
 | 
						|
				if (_dragSelTo->history() == _migrated) {
 | 
						|
					addSelectionRange(toItems, fromblock, fromitem, toblock, toitem, _migrated);
 | 
						|
					toblock = -1;
 | 
						|
					toitem = -1;
 | 
						|
				} else {
 | 
						|
					addSelectionRange(toItems, fromblock, fromitem, _migrated->blocks.size() - 1, _migrated->blocks.back()->items.size() - 1, _migrated);
 | 
						|
				}
 | 
						|
				fromblock = 0;
 | 
						|
				fromitem = 0;
 | 
						|
			} else if (_dragSelTo->history() == _migrated) { // wtf
 | 
						|
				toblock = -1;
 | 
						|
				toitem = -1;
 | 
						|
			}
 | 
						|
		}
 | 
						|
		addSelectionRange(toItems, fromblock, fromitem, toblock, toitem, _history);
 | 
						|
	} else {
 | 
						|
		for (SelectedItems::iterator i = toItems->begin(); i != toItems->cend();) {
 | 
						|
			int32 iy = itemTop(i.key());
 | 
						|
			if (iy < 0) {
 | 
						|
				if (iy < -1) i = toItems->erase(i);
 | 
						|
				continue;
 | 
						|
			}
 | 
						|
			if (iy >= selfromy && iy < seltoy) {
 | 
						|
				i = toItems->erase(i);
 | 
						|
			} else {
 | 
						|
				++i;
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
QString HistoryInner::tooltipText() const {
 | 
						|
	if (_dragCursorState == HistoryInDateCursorState && _dragAction == NoDrag) {
 | 
						|
		if (App::hoveredItem()) {
 | 
						|
			QString dateText = App::hoveredItem()->date.toString(QLocale::system().dateTimeFormat(QLocale::LongFormat));
 | 
						|
			if (auto edited = App::hoveredItem()->Get<HistoryMessageEdited>()) {
 | 
						|
				dateText += '\n' + lng_edited_date(lt_date, edited->_editDate.toString(QLocale::system().dateTimeFormat(QLocale::LongFormat)));
 | 
						|
			}
 | 
						|
			return dateText;
 | 
						|
		}
 | 
						|
	} else if (_dragCursorState == HistoryInForwardedCursorState && _dragAction == NoDrag) {
 | 
						|
		if (App::hoveredItem()) {
 | 
						|
			if (HistoryMessageForwarded *fwd = App::hoveredItem()->Get<HistoryMessageForwarded>()) {
 | 
						|
				return fwd->_text.originalText(AllTextSelection, ExpandLinksNone);
 | 
						|
			}
 | 
						|
		}
 | 
						|
	} else if (ClickHandlerPtr lnk = ClickHandler::getActive()) {
 | 
						|
		return lnk->tooltip();
 | 
						|
	}
 | 
						|
	return QString();
 | 
						|
}
 | 
						|
 | 
						|
QPoint HistoryInner::tooltipPos() const {
 | 
						|
	return _dragPos;
 | 
						|
}
 | 
						|
 | 
						|
void HistoryInner::onParentGeometryChanged() {
 | 
						|
	auto mousePos = QCursor::pos();
 | 
						|
	auto mouseOver = _widget->rect().contains(_widget->mapFromGlobal(mousePos));
 | 
						|
	auto needToUpdate = (_dragAction != NoDrag || _touchScroll || mouseOver);
 | 
						|
	if (needToUpdate) {
 | 
						|
		dragActionUpdate(mousePos);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
MessageField::MessageField(HistoryWidget *history, const style::flatTextarea &st, const QString &ph, const QString &val) : FlatTextarea(history, st, ph, val), history(history) {
 | 
						|
	setMinHeight(st::btnSend.height - 2 * st::sendPadding);
 | 
						|
	setMaxHeight(st::maxFieldHeight);
 | 
						|
}
 | 
						|
 | 
						|
bool MessageField::hasSendText() const {
 | 
						|
	auto &text(getTextWithTags().text);
 | 
						|
	for (auto *ch = text.constData(), *e = ch + text.size(); ch != e; ++ch) {
 | 
						|
		ushort code = ch->unicode();
 | 
						|
		if (code != ' ' && code != '\n' && code != '\r' && !chReplacedBySpace(code)) {
 | 
						|
			return true;
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return false;
 | 
						|
}
 | 
						|
 | 
						|
void MessageField::onEmojiInsert(EmojiPtr emoji) {
 | 
						|
	if (isHidden()) return;
 | 
						|
	insertEmoji(emoji, textCursor());
 | 
						|
}
 | 
						|
 | 
						|
void MessageField::dropEvent(QDropEvent *e) {
 | 
						|
	FlatTextarea::dropEvent(e);
 | 
						|
	if (e->isAccepted()) {
 | 
						|
		App::wnd()->activateWindow();
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
bool MessageField::canInsertFromMimeData(const QMimeData *source) const {
 | 
						|
	if (source->hasUrls()) {
 | 
						|
		int32 files = 0;
 | 
						|
		for (int32 i = 0; i < source->urls().size(); ++i) {
 | 
						|
			if (source->urls().at(i).isLocalFile()) {
 | 
						|
				++files;
 | 
						|
			}
 | 
						|
		}
 | 
						|
		if (files > 1) return false; // multiple confirm with "compressed" checkbox
 | 
						|
	}
 | 
						|
	if (source->hasImage()) return true;
 | 
						|
	return FlatTextarea::canInsertFromMimeData(source);
 | 
						|
}
 | 
						|
 | 
						|
void MessageField::insertFromMimeData(const QMimeData *source) {
 | 
						|
	if (source->hasUrls()) {
 | 
						|
		int32 files = 0;
 | 
						|
		QUrl url;
 | 
						|
		for (int32 i = 0; i < source->urls().size(); ++i) {
 | 
						|
			if (source->urls().at(i).isLocalFile()) {
 | 
						|
				url = source->urls().at(i);
 | 
						|
				++files;
 | 
						|
			}
 | 
						|
		}
 | 
						|
		if (files == 1) {
 | 
						|
			history->uploadFile(url.toLocalFile(), PrepareAuto, FileLoadAlwaysConfirm);
 | 
						|
			return;
 | 
						|
		}
 | 
						|
		if (files > 1) return;
 | 
						|
		//if (files > 1) return uploadFiles(files, PrepareAuto); // multiple confirm with "compressed" checkbox
 | 
						|
	}
 | 
						|
	if (source->hasImage()) {
 | 
						|
		QImage img = qvariant_cast<QImage>(source->imageData());
 | 
						|
		if (!img.isNull()) {
 | 
						|
			history->uploadImage(img, PrepareAuto, FileLoadAlwaysConfirm, source->text());
 | 
						|
			return;
 | 
						|
		}
 | 
						|
	}
 | 
						|
	FlatTextarea::insertFromMimeData(source);
 | 
						|
}
 | 
						|
 | 
						|
void MessageField::focusInEvent(QFocusEvent *e) {
 | 
						|
	FlatTextarea::focusInEvent(e);
 | 
						|
	emit focused();
 | 
						|
}
 | 
						|
 | 
						|
ReportSpamPanel::ReportSpamPanel(HistoryWidget *parent) : TWidget(parent),
 | 
						|
_report(this, lang(lng_report_spam), st::reportSpamHide),
 | 
						|
_hide(this, lang(lng_report_spam_hide), st::reportSpamHide),
 | 
						|
_clear(this, lang(lng_profile_delete_conversation)) {
 | 
						|
	resize(parent->width(), _hide.height() + st::lineWidth);
 | 
						|
 | 
						|
	connect(&_report, SIGNAL(clicked()), this, SIGNAL(reportClicked()));
 | 
						|
	connect(&_hide, SIGNAL(clicked()), this, SIGNAL(hideClicked()));
 | 
						|
	connect(&_clear, SIGNAL(clicked()), this, SIGNAL(clearClicked()));
 | 
						|
 | 
						|
	_clear.hide();
 | 
						|
}
 | 
						|
 | 
						|
void ReportSpamPanel::resizeEvent(QResizeEvent *e) {
 | 
						|
	_report.resize(width() - (_hide.width() + st::reportSpamSeparator) * 2, _report.height());
 | 
						|
	_report.moveToLeft(_hide.width() + st::reportSpamSeparator, 0);
 | 
						|
	_hide.moveToRight(0, 0);
 | 
						|
	_clear.move((width() - _clear.width()) / 2, height() - _clear.height() - ((height() - st::msgFont->height - _clear.height()) / 2));
 | 
						|
}
 | 
						|
 | 
						|
void ReportSpamPanel::paintEvent(QPaintEvent *e) {
 | 
						|
	Painter p(this);
 | 
						|
	p.fillRect(QRect(0, 0, width(), height() - st::lineWidth), st::reportSpamBg->b);
 | 
						|
	p.fillRect(Adaptive::OneColumn() ? 0 : st::lineWidth, height() - st::lineWidth, width() - (Adaptive::OneColumn() ? 0 : st::lineWidth), st::lineWidth, st::shadowColor->b);
 | 
						|
	if (!_clear.isHidden()) {
 | 
						|
		p.setPen(st::black->p);
 | 
						|
		p.setFont(st::msgFont->f);
 | 
						|
		p.drawText(QRect(_report.x(), (_clear.y() - st::msgFont->height) / 2, _report.width(), st::msgFont->height), lang(lng_report_spam_thanks), style::al_top);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void ReportSpamPanel::setReported(bool reported, PeerData *onPeer) {
 | 
						|
	if (reported) {
 | 
						|
		_report.hide();
 | 
						|
		_clear.setText(lang(onPeer->isChannel() ? (onPeer->isMegagroup() ? lng_profile_leave_group : lng_profile_leave_channel) : lng_profile_delete_conversation));
 | 
						|
		_clear.show();
 | 
						|
	} else {
 | 
						|
		_report.show();
 | 
						|
		_clear.hide();
 | 
						|
	}
 | 
						|
	update();
 | 
						|
}
 | 
						|
 | 
						|
BotKeyboard::BotKeyboard() {
 | 
						|
	setGeometry(0, 0, _st->margin, st::botKbScroll.deltat);
 | 
						|
	_height = st::botKbScroll.deltat;
 | 
						|
	setMouseTracking(true);
 | 
						|
}
 | 
						|
 | 
						|
void BotKeyboard::paintEvent(QPaintEvent *e) {
 | 
						|
	Painter p(this);
 | 
						|
 | 
						|
	QRect clip(e->rect());
 | 
						|
	p.fillRect(clip, st::white);
 | 
						|
 | 
						|
	if (_impl) {
 | 
						|
		int x = rtl() ? st::botKbScroll.width : _st->margin;
 | 
						|
		p.translate(x, st::botKbScroll.deltat);
 | 
						|
		_impl->paint(p, clip.translated(-x, -st::botKbScroll.deltat));
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void BotKeyboard::Style::startPaint(Painter &p) const {
 | 
						|
	p.setPen(st::botKbColor);
 | 
						|
	p.setFont(st::botKbFont);
 | 
						|
}
 | 
						|
 | 
						|
style::font BotKeyboard::Style::textFont() const {
 | 
						|
	return st::botKbFont;
 | 
						|
}
 | 
						|
 | 
						|
void BotKeyboard::Style::repaint(const HistoryItem *item) const {
 | 
						|
	_parent->update();
 | 
						|
}
 | 
						|
 | 
						|
void BotKeyboard::Style::paintButtonBg(Painter &p, const QRect &rect, bool down, float64 howMuchOver) const {
 | 
						|
	if (down) {
 | 
						|
		App::roundRect(p, rect, st::botKbDownBg, BotKeyboardDownCorners);
 | 
						|
	} else {
 | 
						|
		App::roundRect(p, rect, st::botKbBg, BotKeyboardCorners);
 | 
						|
		if (howMuchOver > 0) {
 | 
						|
			p.setOpacity(howMuchOver);
 | 
						|
			App::roundRect(p, rect, st::botKbOverBg, BotKeyboardOverCorners);
 | 
						|
			p.setOpacity(1);
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void BotKeyboard::Style::paintButtonIcon(Painter &p, const QRect &rect, HistoryMessageReplyMarkup::Button::Type type) const {
 | 
						|
	// Buttons with icons should not appear here.
 | 
						|
}
 | 
						|
 | 
						|
void BotKeyboard::Style::paintButtonLoading(Painter &p, const QRect &rect) const {
 | 
						|
	// Buttons with loading progress should not appear here.
 | 
						|
}
 | 
						|
 | 
						|
int BotKeyboard::Style::minButtonWidth(HistoryMessageReplyMarkup::Button::Type type) const {
 | 
						|
	int result = 2 * buttonPadding();
 | 
						|
	return result;
 | 
						|
}
 | 
						|
 | 
						|
void BotKeyboard::mousePressEvent(QMouseEvent *e) {
 | 
						|
	_lastMousePos = e->globalPos();
 | 
						|
	updateSelected();
 | 
						|
 | 
						|
	ClickHandler::pressed();
 | 
						|
}
 | 
						|
 | 
						|
void BotKeyboard::mouseMoveEvent(QMouseEvent *e) {
 | 
						|
	_lastMousePos = e->globalPos();
 | 
						|
	updateSelected();
 | 
						|
}
 | 
						|
 | 
						|
void BotKeyboard::mouseReleaseEvent(QMouseEvent *e) {
 | 
						|
	_lastMousePos = e->globalPos();
 | 
						|
	updateSelected();
 | 
						|
 | 
						|
	if (ClickHandlerPtr activated = ClickHandler::unpressed()) {
 | 
						|
		App::activateClickHandler(activated, e->button());
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void BotKeyboard::enterEvent(QEvent *e) {
 | 
						|
	_lastMousePos = QCursor::pos();
 | 
						|
	updateSelected();
 | 
						|
}
 | 
						|
 | 
						|
void BotKeyboard::leaveEvent(QEvent *e) {
 | 
						|
	clearSelection();
 | 
						|
}
 | 
						|
 | 
						|
bool BotKeyboard::moderateKeyActivate(int key) {
 | 
						|
	if (auto item = App::histItemById(_wasForMsgId)) {
 | 
						|
		if (auto markup = item->Get<HistoryMessageReplyMarkup>()) {
 | 
						|
			if (key >= Qt::Key_1 && key <= Qt::Key_9) {
 | 
						|
				int index = (key - Qt::Key_1);
 | 
						|
				if (!markup->rows.isEmpty() && index >= 0 && index < markup->rows.front().size()) {
 | 
						|
					App::activateBotCommand(item, 0, index);
 | 
						|
					return true;
 | 
						|
				}
 | 
						|
			} else if (key == Qt::Key_Q) {
 | 
						|
				if (auto user = item->history()->peer->asUser()) {
 | 
						|
					if (user->botInfo && item->from() == user) {
 | 
						|
						App::sendBotCommand(user, user, qsl("/translate"));
 | 
						|
						return true;
 | 
						|
					}
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return false;
 | 
						|
}
 | 
						|
 | 
						|
void BotKeyboard::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) {
 | 
						|
	if (!_impl) return;
 | 
						|
	_impl->clickHandlerActiveChanged(p, active);
 | 
						|
}
 | 
						|
 | 
						|
void BotKeyboard::clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) {
 | 
						|
	if (!_impl) return;
 | 
						|
	_impl->clickHandlerPressedChanged(p, pressed);
 | 
						|
}
 | 
						|
 | 
						|
bool BotKeyboard::updateMarkup(HistoryItem *to, bool force) {
 | 
						|
	if (!to || !to->definesReplyKeyboard()) {
 | 
						|
		if (_wasForMsgId.msg) {
 | 
						|
			_maximizeSize = _singleUse = _forceReply = false;
 | 
						|
			_wasForMsgId = FullMsgId();
 | 
						|
			_impl = nullptr;
 | 
						|
			return true;
 | 
						|
		}
 | 
						|
		return false;
 | 
						|
	}
 | 
						|
 | 
						|
	if (_wasForMsgId == FullMsgId(to->channelId(), to->id) && !force) {
 | 
						|
		return false;
 | 
						|
	}
 | 
						|
 | 
						|
	_wasForMsgId = FullMsgId(to->channelId(), to->id);
 | 
						|
 | 
						|
	auto markupFlags = to->replyKeyboardFlags();
 | 
						|
	_forceReply = markupFlags & MTPDreplyKeyboardMarkup_ClientFlag::f_force_reply;
 | 
						|
	_maximizeSize = !(markupFlags & MTPDreplyKeyboardMarkup::Flag::f_resize);
 | 
						|
	_singleUse = _forceReply || (markupFlags & MTPDreplyKeyboardMarkup::Flag::f_single_use);
 | 
						|
 | 
						|
	_impl = nullptr;
 | 
						|
	if (auto markup = to->Get<HistoryMessageReplyMarkup>()) {
 | 
						|
		if (!markup->rows.isEmpty()) {
 | 
						|
			_impl.reset(new ReplyKeyboard(to, std_::make_unique<Style>(this, *_st)));
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	resizeToWidth(width(), _maxOuterHeight);
 | 
						|
 | 
						|
	return true;
 | 
						|
}
 | 
						|
 | 
						|
bool BotKeyboard::hasMarkup() const {
 | 
						|
	return _impl != nullptr;
 | 
						|
}
 | 
						|
 | 
						|
bool BotKeyboard::forceReply() const {
 | 
						|
	return _forceReply;
 | 
						|
}
 | 
						|
 | 
						|
int BotKeyboard::resizeGetHeight(int newWidth) {
 | 
						|
	updateStyle(newWidth);
 | 
						|
	_height = st::botKbScroll.deltat + st::botKbScroll.deltab + (_impl ? _impl->naturalHeight() : 0);
 | 
						|
	if (_maximizeSize) {
 | 
						|
		accumulate_max(_height, _maxOuterHeight);
 | 
						|
	}
 | 
						|
	if (_impl) {
 | 
						|
		int implWidth = newWidth - _st->margin - st::botKbScroll.width;
 | 
						|
		int implHeight = _height - (st::botKbScroll.deltat + st::botKbScroll.deltab);
 | 
						|
		_impl->resize(implWidth, implHeight);
 | 
						|
	}
 | 
						|
	return _height;
 | 
						|
}
 | 
						|
 | 
						|
bool BotKeyboard::maximizeSize() const {
 | 
						|
	return _maximizeSize;
 | 
						|
}
 | 
						|
 | 
						|
bool BotKeyboard::singleUse() const {
 | 
						|
	return _singleUse;
 | 
						|
}
 | 
						|
 | 
						|
void BotKeyboard::updateStyle(int newWidth) {
 | 
						|
	if (!_impl) return;
 | 
						|
 | 
						|
	int implWidth = newWidth - st::botKbButton.margin - st::botKbScroll.width;
 | 
						|
	_st = _impl->isEnoughSpace(implWidth, st::botKbButton) ? &st::botKbButton : &st::botKbTinyButton;
 | 
						|
 | 
						|
	_impl->setStyle(std_::make_unique<Style>(this, *_st));
 | 
						|
}
 | 
						|
 | 
						|
void BotKeyboard::clearSelection() {
 | 
						|
	if (_impl) {
 | 
						|
		if (ClickHandler::setActive(ClickHandlerPtr(), this)) {
 | 
						|
			PopupTooltip::Hide();
 | 
						|
			setCursor(style::cur_default);
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
QPoint BotKeyboard::tooltipPos() const {
 | 
						|
	return _lastMousePos;
 | 
						|
}
 | 
						|
 | 
						|
QString BotKeyboard::tooltipText() const {
 | 
						|
	if (ClickHandlerPtr lnk = ClickHandler::getActive()) {
 | 
						|
		return lnk->tooltip();
 | 
						|
	}
 | 
						|
	return QString();
 | 
						|
}
 | 
						|
 | 
						|
void BotKeyboard::updateSelected() {
 | 
						|
	PopupTooltip::Show(1000, this);
 | 
						|
 | 
						|
	if (!_impl) return;
 | 
						|
 | 
						|
	QPoint p(mapFromGlobal(_lastMousePos));
 | 
						|
	int x = rtl() ? st::botKbScroll.width : _st->margin;
 | 
						|
 | 
						|
	auto link = _impl->getState(p.x() - x, p.y() - _st->margin);
 | 
						|
	if (ClickHandler::setActive(link, this)) {
 | 
						|
		PopupTooltip::Hide();
 | 
						|
		setCursor(link ? style::cur_pointer : style::cur_default);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
HistoryHider::HistoryHider(MainWidget *parent, bool forwardSelected) : TWidget(parent)
 | 
						|
, _forwardSelected(forwardSelected)
 | 
						|
, _send(this, lang(lng_forward_send), st::defaultBoxButton)
 | 
						|
, _cancel(this, lang(lng_cancel), st::cancelBoxButton)
 | 
						|
, a_opacity(0, 1)
 | 
						|
, _a_appearance(animation(this, &HistoryHider::step_appearance))
 | 
						|
, _shadow(st::boxShadow) {
 | 
						|
	init();
 | 
						|
}
 | 
						|
 | 
						|
HistoryHider::HistoryHider(MainWidget *parent, UserData *sharedContact) : TWidget(parent)
 | 
						|
, _sharedContact(sharedContact)
 | 
						|
, _send(this, lang(lng_forward_send), st::defaultBoxButton)
 | 
						|
, _cancel(this, lang(lng_cancel), st::cancelBoxButton)
 | 
						|
, a_opacity(0, 1)
 | 
						|
, _a_appearance(animation(this, &HistoryHider::step_appearance))
 | 
						|
, _shadow(st::boxShadow) {
 | 
						|
	init();
 | 
						|
}
 | 
						|
 | 
						|
HistoryHider::HistoryHider(MainWidget *parent) : TWidget(parent)
 | 
						|
, _sendPath(true)
 | 
						|
, _send(this, lang(lng_forward_send), st::defaultBoxButton)
 | 
						|
, _cancel(this, lang(lng_cancel), st::cancelBoxButton)
 | 
						|
, a_opacity(0, 1)
 | 
						|
, _a_appearance(animation(this, &HistoryHider::step_appearance))
 | 
						|
, _shadow(st::boxShadow) {
 | 
						|
	init();
 | 
						|
}
 | 
						|
 | 
						|
HistoryHider::HistoryHider(MainWidget *parent, const QString &botAndQuery) : TWidget(parent)
 | 
						|
, _botAndQuery(botAndQuery)
 | 
						|
, _send(this, lang(lng_forward_send), st::defaultBoxButton)
 | 
						|
, _cancel(this, lang(lng_cancel), st::cancelBoxButton)
 | 
						|
, a_opacity(0, 1)
 | 
						|
, _a_appearance(animation(this, &HistoryHider::step_appearance))
 | 
						|
, _shadow(st::boxShadow) {
 | 
						|
	init();
 | 
						|
}
 | 
						|
 | 
						|
HistoryHider::HistoryHider(MainWidget *parent, const QString &url, const QString &text) : TWidget(parent)
 | 
						|
, _shareUrl(url)
 | 
						|
, _shareText(text)
 | 
						|
, _send(this, lang(lng_forward_send), st::defaultBoxButton)
 | 
						|
, _cancel(this, lang(lng_cancel), st::cancelBoxButton)
 | 
						|
, a_opacity(0, 1)
 | 
						|
, _a_appearance(animation(this, &HistoryHider::step_appearance))
 | 
						|
, _shadow(st::boxShadow) {
 | 
						|
	init();
 | 
						|
}
 | 
						|
 | 
						|
void HistoryHider::init() {
 | 
						|
	connect(&_send, SIGNAL(clicked()), this, SLOT(forward()));
 | 
						|
	connect(&_cancel, SIGNAL(clicked()), this, SLOT(startHide()));
 | 
						|
	connect(App::wnd()->getTitle(), SIGNAL(hiderClicked()), this, SLOT(startHide()));
 | 
						|
 | 
						|
	_chooseWidth = st::forwardFont->width(lang(_botAndQuery.isEmpty() ? lng_forward_choose : lng_inline_switch_choose));
 | 
						|
 | 
						|
	resizeEvent(0);
 | 
						|
	_a_appearance.start();
 | 
						|
}
 | 
						|
 | 
						|
void HistoryHider::step_appearance(float64 ms, bool timer) {
 | 
						|
	float64 dt = ms / 200;
 | 
						|
	if (dt >= 1) {
 | 
						|
		_a_appearance.stop();
 | 
						|
		a_opacity.finish();
 | 
						|
		if (_hiding)	{
 | 
						|
			QTimer::singleShot(0, this, SLOT(deleteLater()));
 | 
						|
		}
 | 
						|
	} else {
 | 
						|
		a_opacity.update(dt, anim::linear);
 | 
						|
	}
 | 
						|
	App::wnd()->getTitle()->setHideLevel(a_opacity.current());
 | 
						|
	if (timer) update();
 | 
						|
}
 | 
						|
 | 
						|
bool HistoryHider::withConfirm() const {
 | 
						|
	return _sharedContact || _sendPath;
 | 
						|
}
 | 
						|
 | 
						|
void HistoryHider::paintEvent(QPaintEvent *e) {
 | 
						|
	Painter p(this);
 | 
						|
	if (!_hiding || !_cacheForAnim.isNull() || !_offered) {
 | 
						|
		p.setOpacity(a_opacity.current() * st::layerAlpha);
 | 
						|
		p.fillRect(rect(), st::layerBg->b);
 | 
						|
		p.setOpacity(a_opacity.current());
 | 
						|
	}
 | 
						|
	if (_cacheForAnim.isNull() || !_offered) {
 | 
						|
		p.setFont(st::forwardFont);
 | 
						|
		if (_offered) {
 | 
						|
			_shadow.paint(p, _box, st::boxShadowShift);
 | 
						|
 | 
						|
			// fill bg
 | 
						|
			p.fillRect(_box, st::boxBg);
 | 
						|
 | 
						|
			p.setPen(st::black);
 | 
						|
			_toText.drawElided(p, _box.left() + st::boxPadding.left(), _box.top() + st::boxPadding.top(), _toTextWidth + 2);
 | 
						|
		} else {
 | 
						|
			int32 w = st::forwardMargins.left() + _chooseWidth + st::forwardMargins.right(), h = st::forwardMargins.top() + st::forwardFont->height + st::forwardMargins.bottom();
 | 
						|
			App::roundRect(p, (width() - w) / 2, (height() - st::titleHeight - h) / 2, w, h, st::forwardBg, ForwardCorners);
 | 
						|
 | 
						|
			p.setPen(st::white);
 | 
						|
			p.drawText(_box, lang(_botAndQuery.isEmpty() ? lng_forward_choose : lng_inline_switch_choose), QTextOption(style::al_center));
 | 
						|
		}
 | 
						|
	} else {
 | 
						|
		p.drawPixmap(_box.left(), _box.top(), _cacheForAnim);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryHider::keyPressEvent(QKeyEvent *e) {
 | 
						|
	if (e->key() == Qt::Key_Escape) {
 | 
						|
		if (_offered) {
 | 
						|
			_offered = nullptr;
 | 
						|
			resizeEvent(nullptr);
 | 
						|
			update();
 | 
						|
			App::main()->dialogsActivate();
 | 
						|
		} else {
 | 
						|
			startHide();
 | 
						|
		}
 | 
						|
	} else if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) {
 | 
						|
		if (_offered) {
 | 
						|
			forward();
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryHider::mousePressEvent(QMouseEvent *e) {
 | 
						|
	if (e->button() == Qt::LeftButton) {
 | 
						|
		if (!_box.contains(e->pos())) {
 | 
						|
			startHide();
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryHider::startHide() {
 | 
						|
	if (_hiding) return;
 | 
						|
	_hiding = true;
 | 
						|
	if (Adaptive::OneColumn()) {
 | 
						|
		QTimer::singleShot(0, this, SLOT(deleteLater()));
 | 
						|
	} else {
 | 
						|
		if (_offered) _cacheForAnim = myGrab(this, _box);
 | 
						|
		if (_forwardRequest) MTP::cancel(_forwardRequest);
 | 
						|
		a_opacity.start(0);
 | 
						|
		_send.hide();
 | 
						|
		_cancel.hide();
 | 
						|
		_a_appearance.start();
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryHider::forward() {
 | 
						|
	if (!_hiding && _offered) {
 | 
						|
		if (_sharedContact) {
 | 
						|
			parent()->onShareContact(_offered->id, _sharedContact);
 | 
						|
		} else if (_sendPath) {
 | 
						|
			parent()->onSendPaths(_offered->id);
 | 
						|
		} else if (!_shareUrl.isEmpty()) {
 | 
						|
			parent()->onShareUrl(_offered->id, _shareUrl, _shareText);
 | 
						|
		} else if (!_botAndQuery.isEmpty()) {
 | 
						|
			parent()->onInlineSwitchChosen(_offered->id, _botAndQuery);
 | 
						|
		} else {
 | 
						|
			parent()->onForward(_offered->id, _forwardSelected ? ForwardSelectedMessages : ForwardContextMessage);
 | 
						|
		}
 | 
						|
	}
 | 
						|
	emit forwarded();
 | 
						|
}
 | 
						|
 | 
						|
void HistoryHider::forwardDone() {
 | 
						|
	_forwardRequest = 0;
 | 
						|
	startHide();
 | 
						|
}
 | 
						|
 | 
						|
MainWidget *HistoryHider::parent() {
 | 
						|
	return static_cast<MainWidget*>(parentWidget());
 | 
						|
}
 | 
						|
 | 
						|
void HistoryHider::resizeEvent(QResizeEvent *e) {
 | 
						|
	int32 w = st::boxWidth, h = st::boxPadding.top() + st::boxPadding.bottom();
 | 
						|
	if (_offered) {
 | 
						|
		if (!_hiding) {
 | 
						|
			_send.show();
 | 
						|
			_cancel.show();
 | 
						|
		}
 | 
						|
		h += st::boxTextFont->height + st::boxButtonPadding.top() + _send.height() + st::boxButtonPadding.bottom();
 | 
						|
	} else {
 | 
						|
		h += st::forwardFont->height;
 | 
						|
		_send.hide();
 | 
						|
		_cancel.hide();
 | 
						|
	}
 | 
						|
	_box = QRect((width() - w) / 2, (height() - st::titleHeight - h) / 2, w, h);
 | 
						|
	_send.moveToRight(width() - (_box.x() + _box.width()) + st::boxButtonPadding.right(), _box.y() + h - st::boxButtonPadding.bottom() - _send.height());
 | 
						|
	_cancel.moveToRight(width() - (_box.x() + _box.width()) + st::boxButtonPadding.right() + _send.width() + st::boxButtonPadding.left(), _send.y());
 | 
						|
}
 | 
						|
 | 
						|
bool HistoryHider::offerPeer(PeerId peer) {
 | 
						|
	if (!peer) {
 | 
						|
		_offered = nullptr;
 | 
						|
		_toText.setText(st::boxTextFont, QString());
 | 
						|
		_toTextWidth = 0;
 | 
						|
		resizeEvent(nullptr);
 | 
						|
		return false;
 | 
						|
	}
 | 
						|
	_offered = App::peer(peer);
 | 
						|
	LangString phrase;
 | 
						|
	QString recipient = _offered->isUser() ? _offered->name : '\xAB' + _offered->name + '\xBB';
 | 
						|
	if (_sharedContact) {
 | 
						|
		phrase = lng_forward_share_contact(lt_recipient, recipient);
 | 
						|
	} else if (_sendPath) {
 | 
						|
		if (cSendPaths().size() > 1) {
 | 
						|
			phrase = lng_forward_send_files_confirm(lt_recipient, recipient);
 | 
						|
		} else {
 | 
						|
			QString name(QFileInfo(cSendPaths().front()).fileName());
 | 
						|
			if (name.size() > 10) {
 | 
						|
				name = name.mid(0, 8) + '.' + '.';
 | 
						|
			}
 | 
						|
			phrase = lng_forward_send_file_confirm(lt_name, name, lt_recipient, recipient);
 | 
						|
		}
 | 
						|
	} else if (!_shareUrl.isEmpty()) {
 | 
						|
		PeerId to = _offered->id;
 | 
						|
		_offered = nullptr;
 | 
						|
		if (parent()->onShareUrl(to, _shareUrl, _shareText)) {
 | 
						|
			startHide();
 | 
						|
		}
 | 
						|
		return false;
 | 
						|
	} else if (!_botAndQuery.isEmpty()) {
 | 
						|
		PeerId to = _offered->id;
 | 
						|
		_offered = nullptr;
 | 
						|
		if (parent()->onInlineSwitchChosen(to, _botAndQuery)) {
 | 
						|
			startHide();
 | 
						|
		}
 | 
						|
		return false;
 | 
						|
	} else {
 | 
						|
		PeerId to = _offered->id;
 | 
						|
		_offered = nullptr;
 | 
						|
		if (parent()->onForward(to, _forwardSelected ? ForwardSelectedMessages : ForwardContextMessage)) {
 | 
						|
			startHide();
 | 
						|
		}
 | 
						|
		return false;
 | 
						|
	}
 | 
						|
 | 
						|
	_toText.setText(st::boxTextFont, phrase, _textNameOptions);
 | 
						|
	_toTextWidth = _toText.maxWidth();
 | 
						|
	if (_toTextWidth > _box.width() - st::boxPadding.left() - st::boxButtonPadding.right()) {
 | 
						|
		_toTextWidth = _box.width() - st::boxPadding.left() - st::boxButtonPadding.right();
 | 
						|
	}
 | 
						|
 | 
						|
	resizeEvent(nullptr);
 | 
						|
	update();
 | 
						|
	setFocus();
 | 
						|
 | 
						|
	return true;
 | 
						|
}
 | 
						|
 | 
						|
QString HistoryHider::offeredText() const {
 | 
						|
	return _toText.originalText();
 | 
						|
}
 | 
						|
 | 
						|
bool HistoryHider::wasOffered() const {
 | 
						|
	return _offered != nullptr;
 | 
						|
}
 | 
						|
 | 
						|
HistoryHider::~HistoryHider() {
 | 
						|
	if (_sendPath) cSetSendPaths(QStringList());
 | 
						|
	if (App::wnd()) App::wnd()->getTitle()->setHideLevel(0);
 | 
						|
	parent()->noHider(this);
 | 
						|
}
 | 
						|
 | 
						|
SilentToggle::SilentToggle(QWidget *parent) : FlatCheckbox(parent, QString(), false, st::silentToggle) {
 | 
						|
	setMouseTracking(true);
 | 
						|
}
 | 
						|
 | 
						|
void SilentToggle::mouseMoveEvent(QMouseEvent *e) {
 | 
						|
	FlatCheckbox::mouseMoveEvent(e);
 | 
						|
	if (rect().contains(e->pos())) {
 | 
						|
		PopupTooltip::Show(1000, this);
 | 
						|
	} else {
 | 
						|
		PopupTooltip::Hide();
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void SilentToggle::leaveEvent(QEvent *e) {
 | 
						|
	PopupTooltip::Hide();
 | 
						|
}
 | 
						|
 | 
						|
void SilentToggle::mouseReleaseEvent(QMouseEvent *e) {
 | 
						|
	FlatCheckbox::mouseReleaseEvent(e);
 | 
						|
	PopupTooltip::Show(0, this);
 | 
						|
	PeerData *p = App::main() ? App::main()->peer() : nullptr;
 | 
						|
	if (p && p->isChannel() && p->notify != UnknownNotifySettings) {
 | 
						|
		App::main()->updateNotifySetting(p, NotifySettingDontChange, checked() ? SilentNotifiesSetSilent : SilentNotifiesSetNotify);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
QString SilentToggle::tooltipText() const {
 | 
						|
	return lang(checked() ? lng_wont_be_notified : lng_will_be_notified);
 | 
						|
}
 | 
						|
 | 
						|
QPoint SilentToggle::tooltipPos() const {
 | 
						|
	return QCursor::pos();
 | 
						|
}
 | 
						|
 | 
						|
EntitiesInText entitiesFromTextTags(const FlatTextarea::TagList &tags) {
 | 
						|
	EntitiesInText result;
 | 
						|
	if (tags.isEmpty()) {
 | 
						|
		return result;
 | 
						|
	}
 | 
						|
 | 
						|
	result.reserve(tags.size());
 | 
						|
	auto mentionStart = qstr("mention://user.");
 | 
						|
	for_const (auto &tag, tags) {
 | 
						|
		if (tag.id.startsWith(mentionStart)) {
 | 
						|
			if (auto match = qthelp::regex_match("^(\\d+\\.\\d+)(/|$)", tag.id.midRef(mentionStart.size()))) {
 | 
						|
				result.push_back(EntityInText(EntityInTextMentionName, tag.offset, tag.length, match->captured(1)));
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return result;
 | 
						|
}
 | 
						|
 | 
						|
TextWithTags::Tags textTagsFromEntities(const EntitiesInText &entities) {
 | 
						|
	TextWithTags::Tags result;
 | 
						|
	if (entities.isEmpty()) {
 | 
						|
		return result;
 | 
						|
	}
 | 
						|
 | 
						|
	result.reserve(entities.size());
 | 
						|
	for_const (auto &entity, entities) {
 | 
						|
		if (entity.type() == EntityInTextMentionName) {
 | 
						|
			auto match = QRegularExpression("^(\\d+\\.\\d+)$").match(entity.data());
 | 
						|
			if (match.hasMatch()) {
 | 
						|
				result.push_back({ entity.offset(), entity.length(), qstr("mention://user.") + entity.data() });
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return result;
 | 
						|
}
 | 
						|
 | 
						|
HistoryWidget::HistoryWidget(QWidget *parent) : TWidget(parent)
 | 
						|
, _fieldBarCancel(this, st::replyCancel)
 | 
						|
, _scroll(this, st::historyScroll, false)
 | 
						|
, _historyToEnd(this)
 | 
						|
, _fieldAutocomplete(this)
 | 
						|
, _reportSpamPanel(this)
 | 
						|
, _send(this, lang(lng_send_button), st::btnSend)
 | 
						|
, _unblock(this, lang(lng_unblock_button), st::btnUnblock)
 | 
						|
, _botStart(this, lang(lng_bot_start), st::btnSend)
 | 
						|
, _joinChannel(this, lang(lng_channel_join), st::btnSend)
 | 
						|
, _muteUnmute(this, lang(lng_channel_mute), st::btnSend)
 | 
						|
, _attachDocument(this, st::btnAttachDocument)
 | 
						|
, _attachPhoto(this, st::btnAttachPhoto)
 | 
						|
, _attachEmoji(this, st::btnAttachEmoji)
 | 
						|
, _kbShow(this, st::btnBotKbShow)
 | 
						|
, _kbHide(this, st::btnBotKbHide)
 | 
						|
, _cmdStart(this, st::btnBotCmdStart)
 | 
						|
, _silent(this)
 | 
						|
, _field(this, st::taMsgField, lang(lng_message_ph))
 | 
						|
, _a_record(animation(this, &HistoryWidget::step_record))
 | 
						|
, _a_recording(animation(this, &HistoryWidget::step_recording))
 | 
						|
, a_recordCancel(st::recordCancel->c, st::recordCancel->c)
 | 
						|
, _recordCancelWidth(st::recordFont->width(lang(lng_record_cancel)))
 | 
						|
, _kbScroll(this, st::botKbScroll)
 | 
						|
, _attachType(this)
 | 
						|
, _emojiPan(this)
 | 
						|
, _attachDragDocument(this)
 | 
						|
, _attachDragPhoto(this)
 | 
						|
, _fileLoader(this, FileLoaderQueueStopTimeout)
 | 
						|
, _a_show(animation(this, &HistoryWidget::step_show))
 | 
						|
, _topShadow(this, st::shadowColor) {
 | 
						|
	_scroll.setFocusPolicy(Qt::NoFocus);
 | 
						|
 | 
						|
	setAcceptDrops(true);
 | 
						|
 | 
						|
	subscribe(FileDownload::ImageLoaded(), [this] { update(); });
 | 
						|
	connect(&_scroll, SIGNAL(scrolled()), this, SLOT(onScroll()));
 | 
						|
	connect(&_reportSpamPanel, SIGNAL(reportClicked()), this, SLOT(onReportSpamClicked()));
 | 
						|
	connect(&_reportSpamPanel, SIGNAL(hideClicked()), this, SLOT(onReportSpamHide()));
 | 
						|
	connect(&_reportSpamPanel, SIGNAL(clearClicked()), this, SLOT(onReportSpamClear()));
 | 
						|
	connect(_historyToEnd, SIGNAL(clicked()), this, SLOT(onHistoryToEnd()));
 | 
						|
	connect(&_fieldBarCancel, SIGNAL(clicked()), this, SLOT(onFieldBarCancel()));
 | 
						|
	connect(&_send, SIGNAL(clicked()), this, SLOT(onSend()));
 | 
						|
	connect(&_unblock, SIGNAL(clicked()), this, SLOT(onUnblock()));
 | 
						|
	connect(&_botStart, SIGNAL(clicked()), this, SLOT(onBotStart()));
 | 
						|
	connect(&_joinChannel, SIGNAL(clicked()), this, SLOT(onJoinChannel()));
 | 
						|
	connect(&_muteUnmute, SIGNAL(clicked()), this, SLOT(onMuteUnmute()));
 | 
						|
	connect(&_silent, SIGNAL(clicked()), this, SLOT(onBroadcastSilentChange()));
 | 
						|
	connect(&_attachDocument, SIGNAL(clicked()), this, SLOT(onDocumentSelect()));
 | 
						|
	connect(&_attachPhoto, SIGNAL(clicked()), this, SLOT(onPhotoSelect()));
 | 
						|
	connect(&_field, SIGNAL(submitted(bool)), this, SLOT(onSend(bool)));
 | 
						|
	connect(&_field, SIGNAL(cancelled()), this, SLOT(onCancel()));
 | 
						|
	connect(&_field, SIGNAL(tabbed()), this, SLOT(onFieldTabbed()));
 | 
						|
	connect(&_field, SIGNAL(resized()), this, SLOT(onFieldResize()));
 | 
						|
	connect(&_field, SIGNAL(focused()), this, SLOT(onFieldFocused()));
 | 
						|
	connect(&_field, SIGNAL(changed()), this, SLOT(onTextChange()));
 | 
						|
	connect(&_field, SIGNAL(spacedReturnedPasted()), this, SLOT(onPreviewParse()));
 | 
						|
	connect(&_field, SIGNAL(linksChanged()), this, SLOT(onPreviewCheck()));
 | 
						|
	connect(App::wnd()->windowHandle(), SIGNAL(visibleChanged(bool)), this, SLOT(onWindowVisibleChanged()));
 | 
						|
	connect(&_scrollTimer, SIGNAL(timeout()), this, SLOT(onScrollTimer()));
 | 
						|
	connect(_emojiPan, SIGNAL(emojiSelected(EmojiPtr)), &_field, SLOT(onEmojiInsert(EmojiPtr)));
 | 
						|
	connect(_emojiPan, SIGNAL(stickerSelected(DocumentData*)), this, SLOT(onStickerSend(DocumentData*)));
 | 
						|
	connect(_emojiPan, SIGNAL(photoSelected(PhotoData*)), this, SLOT(onPhotoSend(PhotoData*)));
 | 
						|
	connect(_emojiPan, SIGNAL(inlineResultSelected(InlineBots::Result*,UserData*)), this, SLOT(onInlineResultSend(InlineBots::Result*,UserData*)));
 | 
						|
	connect(_emojiPan, SIGNAL(updateStickers()), this, SLOT(updateStickers()));
 | 
						|
	connect(&_sendActionStopTimer, SIGNAL(timeout()), this, SLOT(onCancelSendAction()));
 | 
						|
	connect(&_previewTimer, SIGNAL(timeout()), this, SLOT(onPreviewTimeout()));
 | 
						|
	if (audioCapture()) {
 | 
						|
		connect(audioCapture(), SIGNAL(error()), this, SLOT(onRecordError()));
 | 
						|
		connect(audioCapture(), SIGNAL(updated(quint16,qint32)), this, SLOT(onRecordUpdate(quint16,qint32)));
 | 
						|
		connect(audioCapture(), SIGNAL(done(QByteArray,VoiceWaveform,qint32)), this, SLOT(onRecordDone(QByteArray,VoiceWaveform,qint32)));
 | 
						|
	}
 | 
						|
 | 
						|
	_updateHistoryItems.setSingleShot(true);
 | 
						|
	connect(&_updateHistoryItems, SIGNAL(timeout()), this, SLOT(onUpdateHistoryItems()));
 | 
						|
 | 
						|
	_scrollTimer.setSingleShot(false);
 | 
						|
 | 
						|
	_sendActionStopTimer.setSingleShot(true);
 | 
						|
 | 
						|
	_animActiveTimer.setSingleShot(false);
 | 
						|
	connect(&_animActiveTimer, SIGNAL(timeout()), this, SLOT(onAnimActiveStep()));
 | 
						|
 | 
						|
	_membersDropdownShowTimer.setSingleShot(true);
 | 
						|
	connect(&_membersDropdownShowTimer, SIGNAL(timeout()), this, SLOT(onMembersDropdownShow()));
 | 
						|
 | 
						|
	_saveDraftTimer.setSingleShot(true);
 | 
						|
	connect(&_saveDraftTimer, SIGNAL(timeout()), this, SLOT(onDraftSave()));
 | 
						|
	_saveCloudDraftTimer.setSingleShot(true);
 | 
						|
	connect(&_saveCloudDraftTimer, SIGNAL(timeout()), this, SLOT(onCloudDraftSave()));
 | 
						|
	connect(_field.verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(onDraftSaveDelayed()));
 | 
						|
	connect(&_field, SIGNAL(cursorPositionChanged()), this, SLOT(onDraftSaveDelayed()));
 | 
						|
	connect(&_field, SIGNAL(cursorPositionChanged()), this, SLOT(onCheckFieldAutocomplete()), Qt::QueuedConnection);
 | 
						|
 | 
						|
	_fieldBarCancel.hide();
 | 
						|
 | 
						|
	_scroll.hide();
 | 
						|
 | 
						|
	_kbScroll.setFocusPolicy(Qt::NoFocus);
 | 
						|
	_kbScroll.viewport()->setFocusPolicy(Qt::NoFocus);
 | 
						|
	_kbScroll.setWidget(&_keyboard);
 | 
						|
	_kbScroll.hide();
 | 
						|
 | 
						|
	updateScrollColors();
 | 
						|
 | 
						|
	_historyToEnd->installEventFilter(this);
 | 
						|
 | 
						|
	_fieldAutocomplete->hide();
 | 
						|
	connect(_fieldAutocomplete, SIGNAL(mentionChosen(UserData*,FieldAutocomplete::ChooseMethod)), this, SLOT(onMentionInsert(UserData*)));
 | 
						|
	connect(_fieldAutocomplete, SIGNAL(hashtagChosen(QString,FieldAutocomplete::ChooseMethod)), this, SLOT(onHashtagOrBotCommandInsert(QString,FieldAutocomplete::ChooseMethod)));
 | 
						|
	connect(_fieldAutocomplete, SIGNAL(botCommandChosen(QString,FieldAutocomplete::ChooseMethod)), this, SLOT(onHashtagOrBotCommandInsert(QString,FieldAutocomplete::ChooseMethod)));
 | 
						|
	connect(_fieldAutocomplete, SIGNAL(stickerChosen(DocumentData*,FieldAutocomplete::ChooseMethod)), this, SLOT(onStickerSend(DocumentData*)));
 | 
						|
	connect(_fieldAutocomplete, SIGNAL(moderateKeyActivate(int,bool*)), this, SLOT(onModerateKeyActivate(int,bool*)));
 | 
						|
	_field.installEventFilter(_fieldAutocomplete);
 | 
						|
	_field.setTagMimeProcessor(std_::make_unique<FieldTagMimeProcessor>());
 | 
						|
	updateFieldSubmitSettings();
 | 
						|
 | 
						|
	_field.hide();
 | 
						|
	_send.hide();
 | 
						|
	_unblock.hide();
 | 
						|
	_botStart.hide();
 | 
						|
	_joinChannel.hide();
 | 
						|
	_muteUnmute.hide();
 | 
						|
 | 
						|
	_reportSpamPanel.move(0, 0);
 | 
						|
	_reportSpamPanel.hide();
 | 
						|
 | 
						|
	_attachDocument.hide();
 | 
						|
	_attachPhoto.hide();
 | 
						|
	_attachEmoji.hide();
 | 
						|
	_kbShow.hide();
 | 
						|
	_kbHide.hide();
 | 
						|
	_silent.hide();
 | 
						|
	_cmdStart.hide();
 | 
						|
 | 
						|
	_attachDocument.installEventFilter(_attachType);
 | 
						|
	_attachPhoto.installEventFilter(_attachType);
 | 
						|
	_attachEmoji.installEventFilter(_emojiPan);
 | 
						|
 | 
						|
	connect(&_kbShow, SIGNAL(clicked()), this, SLOT(onKbToggle()));
 | 
						|
	connect(&_kbHide, SIGNAL(clicked()), this, SLOT(onKbToggle()));
 | 
						|
	connect(&_cmdStart, SIGNAL(clicked()), this, SLOT(onCmdStart()));
 | 
						|
 | 
						|
	connect(_attachType->addButton(new IconedButton(this, st::dropdownAttachDocument, lang(lng_attach_file))), SIGNAL(clicked()), this, SLOT(onDocumentSelect()));
 | 
						|
	connect(_attachType->addButton(new IconedButton(this, st::dropdownAttachPhoto, lang(lng_attach_photo))), SIGNAL(clicked()), this, SLOT(onPhotoSelect()));
 | 
						|
	_attachType->hide();
 | 
						|
	_emojiPan->hide();
 | 
						|
	_attachDragDocument->hide();
 | 
						|
	_attachDragPhoto->hide();
 | 
						|
 | 
						|
	_topShadow->hide();
 | 
						|
 | 
						|
	connect(_attachDragDocument, SIGNAL(dropped(const QMimeData*)), this, SLOT(onDocumentDrop(const QMimeData*)));
 | 
						|
	connect(_attachDragPhoto, SIGNAL(dropped(const QMimeData*)), this, SLOT(onPhotoDrop(const QMimeData*)));
 | 
						|
 | 
						|
	connect(&_updateEditTimeLeftDisplay, SIGNAL(timeout()), this, SLOT(updateField()));
 | 
						|
 | 
						|
	subscribe(Adaptive::Changed(), [this] { update(); });
 | 
						|
	subscribe(Global::RefItemRemoved(), [this](HistoryItem *item) {
 | 
						|
		itemRemoved(item);
 | 
						|
	});
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::start() {
 | 
						|
	connect(App::main(), SIGNAL(stickersUpdated()), this, SLOT(onStickersUpdated()));
 | 
						|
	connect(App::main(), SIGNAL(savedGifsUpdated()), _emojiPan, SLOT(refreshSavedGifs()));
 | 
						|
 | 
						|
	updateRecentStickers();
 | 
						|
	if (App::main()) emit App::main()->savedGifsUpdated();
 | 
						|
 | 
						|
	connect(App::api(), SIGNAL(fullPeerUpdated(PeerData*)), this, SLOT(onFullPeerUpdated(PeerData*)));
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::onStickersUpdated() {
 | 
						|
	_emojiPan->refreshStickers();
 | 
						|
	updateStickersByEmoji();
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::onMentionInsert(UserData *user) {
 | 
						|
	QString replacement, entityTag;
 | 
						|
	if (user->username.isEmpty()) {
 | 
						|
		replacement = user->firstName;
 | 
						|
		if (replacement.isEmpty()) {
 | 
						|
			replacement = App::peerName(user);
 | 
						|
		}
 | 
						|
		entityTag = qsl("mention://user.") + QString::number(user->bareId()) + '.' + QString::number(user->access);
 | 
						|
	} else {
 | 
						|
		replacement = '@' + user->username;
 | 
						|
	}
 | 
						|
	_field.insertTag(replacement, entityTag);
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::onHashtagOrBotCommandInsert(QString str, FieldAutocomplete::ChooseMethod method) {
 | 
						|
	// Send bot command at once, if it was not inserted by pressing Tab.
 | 
						|
	if (str.at(0) == '/' && method != FieldAutocomplete::ChooseMethod::ByTab) {
 | 
						|
		App::sendBotCommand(_peer, nullptr, str);
 | 
						|
		setFieldText(_field.getTextWithTagsPart(_field.textCursor().position()));
 | 
						|
	} else {
 | 
						|
		_field.insertTag(str);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::updateInlineBotQuery() {
 | 
						|
	UserData *bot = nullptr;
 | 
						|
	QString inlineBotUsername;
 | 
						|
	QString query = _field.getInlineBotQuery(&bot, &inlineBotUsername);
 | 
						|
	if (inlineBotUsername != _inlineBotUsername) {
 | 
						|
		_inlineBotUsername = inlineBotUsername;
 | 
						|
		if (_inlineBotResolveRequestId) {
 | 
						|
//			Notify::inlineBotRequesting(false);
 | 
						|
			MTP::cancel(_inlineBotResolveRequestId);
 | 
						|
			_inlineBotResolveRequestId = 0;
 | 
						|
		}
 | 
						|
		if (bot == LookingUpInlineBot) {
 | 
						|
			_inlineBot = LookingUpInlineBot;
 | 
						|
//			Notify::inlineBotRequesting(true);
 | 
						|
			_inlineBotResolveRequestId = MTP::send(MTPcontacts_ResolveUsername(MTP_string(_inlineBotUsername)), rpcDone(&HistoryWidget::inlineBotResolveDone), rpcFail(&HistoryWidget::inlineBotResolveFail, _inlineBotUsername));
 | 
						|
			return;
 | 
						|
		}
 | 
						|
	} else if (bot == LookingUpInlineBot) {
 | 
						|
		if (_inlineBot == LookingUpInlineBot) {
 | 
						|
			return;
 | 
						|
		}
 | 
						|
		bot = _inlineBot;
 | 
						|
	}
 | 
						|
 | 
						|
	applyInlineBotQuery(bot, query);
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::applyInlineBotQuery(UserData *bot, const QString &query) {
 | 
						|
	if (bot) {
 | 
						|
		if (_inlineBot != bot) {
 | 
						|
			_inlineBot = bot;
 | 
						|
			inlineBotChanged();
 | 
						|
		}
 | 
						|
		if (_inlineBot->username == cInlineGifBotUsername() && query.isEmpty()) {
 | 
						|
			_emojiPan->clearInlineBot();
 | 
						|
		} else {
 | 
						|
			_emojiPan->queryInlineBot(_inlineBot, _peer, query);
 | 
						|
		}
 | 
						|
		if (!_fieldAutocomplete->isHidden()) {
 | 
						|
			_fieldAutocomplete->hideStart();
 | 
						|
		}
 | 
						|
	} else {
 | 
						|
		clearInlineBot();
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::updateStickersByEmoji() {
 | 
						|
	int len = 0;
 | 
						|
	if (!_editMsgId) {
 | 
						|
		auto &text = _field.getTextWithTags().text;
 | 
						|
		if (auto emoji = emojiFromText(text, &len)) {
 | 
						|
			if (text.size() > len) {
 | 
						|
				len = 0;
 | 
						|
			} else {
 | 
						|
				_fieldAutocomplete->showStickers(emoji);
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if (!len) {
 | 
						|
		_fieldAutocomplete->showStickers(nullptr);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::onTextChange() {
 | 
						|
	updateInlineBotQuery();
 | 
						|
	updateStickersByEmoji();
 | 
						|
 | 
						|
	if (_peer && (!_peer->isChannel() || _peer->isMegagroup())) {
 | 
						|
		if (!_inlineBot && !_editMsgId && (_textUpdateEvents.testFlag(TextUpdateEvent::SendTyping))) {
 | 
						|
			updateSendAction(_history, SendActionTyping);
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if (cHasAudioCapture()) {
 | 
						|
		if (!_field.hasSendText() && !readyToForward() && !_editMsgId) {
 | 
						|
			_previewCancelled = false;
 | 
						|
			_send.hide();
 | 
						|
			updateMouseTracking();
 | 
						|
			mouseMoveEvent(0);
 | 
						|
		} else if (!_field.isHidden() && _send.isHidden()) {
 | 
						|
			_send.show();
 | 
						|
			updateMouseTracking();
 | 
						|
			_a_record.stop();
 | 
						|
			_inRecord = _inField = false;
 | 
						|
			a_recordOver = a_recordDown = anim::fvalue(0, 0);
 | 
						|
			a_recordCancel = anim::cvalue(st::recordCancel->c, st::recordCancel->c);
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if (updateCmdStartShown()) {
 | 
						|
		updateControlsVisibility();
 | 
						|
		updateControlsGeometry();
 | 
						|
	}
 | 
						|
 | 
						|
	_saveCloudDraftTimer.stop();
 | 
						|
	if (!_peer || !(_textUpdateEvents.testFlag(TextUpdateEvent::SaveDraft))) return;
 | 
						|
 | 
						|
	_saveDraftText = true;
 | 
						|
	onDraftSave(true);
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::onDraftSaveDelayed() {
 | 
						|
	if (!_peer || !(_textUpdateEvents.testFlag(TextUpdateEvent::SaveDraft))) return;
 | 
						|
	if (!_field.textCursor().anchor() && !_field.textCursor().position() && !_field.verticalScrollBar()->value()) {
 | 
						|
		if (!Local::hasDraftCursors(_peer->id)) {
 | 
						|
			return;
 | 
						|
		}
 | 
						|
	}
 | 
						|
	onDraftSave(true);
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::onDraftSave(bool delayed) {
 | 
						|
	if (!_peer) return;
 | 
						|
	if (delayed) {
 | 
						|
		uint64 ms = getms();
 | 
						|
		if (!_saveDraftStart) {
 | 
						|
			_saveDraftStart = ms;
 | 
						|
			return _saveDraftTimer.start(SaveDraftTimeout);
 | 
						|
		} else if (ms - _saveDraftStart < SaveDraftAnywayTimeout) {
 | 
						|
			return _saveDraftTimer.start(SaveDraftTimeout);
 | 
						|
		}
 | 
						|
	}
 | 
						|
	writeDrafts(nullptr, nullptr);
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::saveFieldToHistoryLocalDraft() {
 | 
						|
	if (!_history) return;
 | 
						|
 | 
						|
	if (_editMsgId) {
 | 
						|
		_history->setEditDraft(std_::make_unique<Data::Draft>(_field, _editMsgId, _previewCancelled, _saveEditMsgRequestId));
 | 
						|
	} else {
 | 
						|
		if (_replyToId || !_field.isEmpty()) {
 | 
						|
			_history->setLocalDraft(std_::make_unique<Data::Draft>(_field, _replyToId, _previewCancelled));
 | 
						|
		} else {
 | 
						|
			_history->clearLocalDraft();
 | 
						|
		}
 | 
						|
		_history->clearEditDraft();
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::onCloudDraftSave() {
 | 
						|
	if (App::main()) {
 | 
						|
		App::main()->saveDraftToCloud();
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::writeDrafts(Data::Draft **localDraft, Data::Draft **editDraft) {
 | 
						|
	Data::Draft *historyLocalDraft = _history ? _history->localDraft() : nullptr;
 | 
						|
	if (!localDraft && _editMsgId) localDraft = &historyLocalDraft;
 | 
						|
 | 
						|
	bool save = _peer && (_saveDraftStart > 0);
 | 
						|
	_saveDraftStart = 0;
 | 
						|
	_saveDraftTimer.stop();
 | 
						|
	if (_saveDraftText) {
 | 
						|
		if (save) {
 | 
						|
			Local::MessageDraft storedLocalDraft, storedEditDraft;
 | 
						|
			if (localDraft) {
 | 
						|
				if (*localDraft) {
 | 
						|
					storedLocalDraft = Local::MessageDraft((*localDraft)->msgId, (*localDraft)->textWithTags, (*localDraft)->previewCancelled);
 | 
						|
				}
 | 
						|
			} else {
 | 
						|
				storedLocalDraft = Local::MessageDraft(_replyToId, _field.getTextWithTags(), _previewCancelled);
 | 
						|
			}
 | 
						|
			if (editDraft) {
 | 
						|
				if (*editDraft) {
 | 
						|
					storedEditDraft = Local::MessageDraft((*editDraft)->msgId, (*editDraft)->textWithTags, (*editDraft)->previewCancelled);
 | 
						|
				}
 | 
						|
			} else if (_editMsgId) {
 | 
						|
				storedEditDraft = Local::MessageDraft(_editMsgId, _field.getTextWithTags(), _previewCancelled);
 | 
						|
			}
 | 
						|
			Local::writeDrafts(_peer->id, storedLocalDraft, storedEditDraft);
 | 
						|
			if (_migrated) {
 | 
						|
				Local::writeDrafts(_migrated->peer->id, Local::MessageDraft(), Local::MessageDraft());
 | 
						|
			}
 | 
						|
		}
 | 
						|
		_saveDraftText = false;
 | 
						|
	}
 | 
						|
	if (save) {
 | 
						|
		MessageCursor localCursor, editCursor;
 | 
						|
		if (localDraft) {
 | 
						|
			if (*localDraft) {
 | 
						|
				localCursor = (*localDraft)->cursor;
 | 
						|
			}
 | 
						|
		} else {
 | 
						|
			localCursor = MessageCursor(_field);
 | 
						|
		}
 | 
						|
		if (editDraft) {
 | 
						|
			if (*editDraft) {
 | 
						|
				editCursor = (*editDraft)->cursor;
 | 
						|
			}
 | 
						|
		} else if (_editMsgId) {
 | 
						|
			editCursor = MessageCursor(_field);
 | 
						|
		}
 | 
						|
		Local::writeDraftCursors(_peer->id, localCursor, editCursor);
 | 
						|
		if (_migrated) {
 | 
						|
			Local::writeDraftCursors(_migrated->peer->id, MessageCursor(), MessageCursor());
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if (!_editMsgId && !_inlineBot) {
 | 
						|
		_saveCloudDraftTimer.start(SaveCloudDraftIdleTimeout);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::cancelSendAction(History *history, SendActionType type) {
 | 
						|
	QMap<QPair<History*, SendActionType>, mtpRequestId>::iterator i = _sendActionRequests.find(qMakePair(history, type));
 | 
						|
	if (i != _sendActionRequests.cend()) {
 | 
						|
		MTP::cancel(i.value());
 | 
						|
		_sendActionRequests.erase(i);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::onCancelSendAction() {
 | 
						|
	cancelSendAction(_history, SendActionTyping);
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::updateSendAction(History *history, SendActionType type, int32 progress) {
 | 
						|
	if (!history) return;
 | 
						|
 | 
						|
	bool doing = (progress >= 0);
 | 
						|
 | 
						|
	uint64 ms = getms(true) + 10000;
 | 
						|
	QMap<SendActionType, uint64>::iterator i = history->mySendActions.find(type);
 | 
						|
	if (doing && i != history->mySendActions.cend() && i.value() + 5000 > ms) return;
 | 
						|
	if (!doing && (i == history->mySendActions.cend() || i.value() + 5000 <= ms)) return;
 | 
						|
 | 
						|
	if (doing) {
 | 
						|
		if (i == history->mySendActions.cend()) {
 | 
						|
			history->mySendActions.insert(type, ms);
 | 
						|
		} else {
 | 
						|
			i.value() = ms;
 | 
						|
		}
 | 
						|
	} else if (i != history->mySendActions.cend()) {
 | 
						|
		history->mySendActions.erase(i);
 | 
						|
	}
 | 
						|
 | 
						|
	cancelSendAction(history, type);
 | 
						|
	if (doing) {
 | 
						|
		MTPsendMessageAction action;
 | 
						|
		switch (type) {
 | 
						|
		case SendActionTyping: action = MTP_sendMessageTypingAction(); break;
 | 
						|
		case SendActionRecordVideo: action = MTP_sendMessageRecordVideoAction(); break;
 | 
						|
		case SendActionUploadVideo: action = MTP_sendMessageUploadVideoAction(MTP_int(progress)); break;
 | 
						|
		case SendActionRecordVoice: action = MTP_sendMessageRecordAudioAction(); break;
 | 
						|
		case SendActionUploadVoice: action = MTP_sendMessageUploadAudioAction(MTP_int(progress)); break;
 | 
						|
		case SendActionUploadPhoto: action = MTP_sendMessageUploadPhotoAction(MTP_int(progress)); break;
 | 
						|
		case SendActionUploadFile: action = MTP_sendMessageUploadDocumentAction(MTP_int(progress)); break;
 | 
						|
		case SendActionChooseLocation: action = MTP_sendMessageGeoLocationAction(); break;
 | 
						|
		case SendActionChooseContact: action = MTP_sendMessageChooseContactAction(); break;
 | 
						|
		case SendActionPlayGame: action = MTP_sendMessageGamePlayAction(); break;
 | 
						|
		}
 | 
						|
		_sendActionRequests.insert(qMakePair(history, type), MTP::send(MTPmessages_SetTyping(history->peer->input, action), rpcDone(&HistoryWidget::sendActionDone)));
 | 
						|
		if (type == SendActionTyping) _sendActionStopTimer.start(5000);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::updateRecentStickers() {
 | 
						|
	_emojiPan->refreshStickers();
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::stickersInstalled(uint64 setId) {
 | 
						|
	_emojiPan->stickersInstalled(setId);
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::sendActionDone(const MTPBool &result, mtpRequestId req) {
 | 
						|
	for (QMap<QPair<History*, SendActionType>, mtpRequestId>::iterator i = _sendActionRequests.begin(), e = _sendActionRequests.end(); i != e; ++i) {
 | 
						|
		if (i.value() == req) {
 | 
						|
			_sendActionRequests.erase(i);
 | 
						|
			break;
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::activate() {
 | 
						|
	if (_history) {
 | 
						|
		if (!_histInited) {
 | 
						|
			updateListSize(true);
 | 
						|
		} else if (hasPendingResizedItems()) {
 | 
						|
			updateListSize();
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if (App::wnd()) App::wnd()->setInnerFocus();
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::setInnerFocus() {
 | 
						|
	if (_scroll.isHidden()) {
 | 
						|
		setFocus();
 | 
						|
	} else if (_list) {
 | 
						|
		if (_selCount || (_list && _list->wasSelectedText()) || _recording || isBotStart() || isBlocked() || !_canSendMessages) {
 | 
						|
			_list->setFocus();
 | 
						|
		} else {
 | 
						|
			_field.setFocus();
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::onRecordError() {
 | 
						|
	stopRecording(false);
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::onRecordDone(QByteArray result, VoiceWaveform waveform, qint32 samples) {
 | 
						|
	if (!_peer || result.isEmpty()) return;
 | 
						|
 | 
						|
	App::wnd()->activateWindow();
 | 
						|
	int32 duration = samples / AudioVoiceMsgFrequency;
 | 
						|
	_fileLoader.addTask(new FileLoadTask(result, duration, waveform, FileLoadTo(_peer->id, _silent.checked(), replyToId())));
 | 
						|
	cancelReplyAfterMediaSend(lastForceReplyReplied());
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::onRecordUpdate(quint16 level, qint32 samples) {
 | 
						|
	if (!_recording) {
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	a_recordingLevel.start(level);
 | 
						|
	_a_recording.start();
 | 
						|
	_recordingSamples = samples;
 | 
						|
	if (samples < 0 || samples >= AudioVoiceMsgFrequency * AudioVoiceMsgMaxLength) {
 | 
						|
		stopRecording(_peer && samples > 0 && _inField);
 | 
						|
	}
 | 
						|
	updateField();
 | 
						|
	if (_peer && (!_peer->isChannel() || _peer->isMegagroup())) {
 | 
						|
		updateSendAction(_history, SendActionRecordVoice);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::updateStickers() {
 | 
						|
	auto now = getms(true);
 | 
						|
	if (!Global::LastStickersUpdate() || now >= Global::LastStickersUpdate() + StickersUpdateTimeout) {
 | 
						|
		if (!_stickersUpdateRequest) {
 | 
						|
			_stickersUpdateRequest = MTP::send(MTPmessages_GetAllStickers(MTP_int(Local::countStickersHash(true))), rpcDone(&HistoryWidget::stickersGot), rpcFail(&HistoryWidget::stickersFailed));
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if (!Global::LastRecentStickersUpdate() || now >= Global::LastRecentStickersUpdate() + StickersUpdateTimeout) {
 | 
						|
		if (!_recentStickersUpdateRequest) {
 | 
						|
			MTPmessages_GetRecentStickers::Flags flags = 0;
 | 
						|
			_recentStickersUpdateRequest = MTP::send(MTPmessages_GetRecentStickers(MTP_flags(flags), MTP_int(Local::countRecentStickersHash())), rpcDone(&HistoryWidget::recentStickersGot), rpcFail(&HistoryWidget::recentStickersFailed));
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if (!Global::LastFeaturedStickersUpdate() || now >= Global::LastFeaturedStickersUpdate() + StickersUpdateTimeout) {
 | 
						|
		if (!_featuredStickersUpdateRequest) {
 | 
						|
			_featuredStickersUpdateRequest = MTP::send(MTPmessages_GetFeaturedStickers(MTP_int(Local::countFeaturedStickersHash())), rpcDone(&HistoryWidget::featuredStickersGot), rpcFail(&HistoryWidget::featuredStickersFailed));
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if (!cLastSavedGifsUpdate() || now >= cLastSavedGifsUpdate() + StickersUpdateTimeout) {
 | 
						|
		if (!_savedGifsUpdateRequest) {
 | 
						|
			_savedGifsUpdateRequest = MTP::send(MTPmessages_GetSavedGifs(MTP_int(Local::countSavedGifsHash())), rpcDone(&HistoryWidget::savedGifsGot), rpcFail(&HistoryWidget::savedGifsFailed));
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::notify_botCommandsChanged(UserData *user) {
 | 
						|
	if (_peer && (_peer == user || !_peer->isUser())) {
 | 
						|
		if (_fieldAutocomplete->clearFilteredBotCommands()) {
 | 
						|
			onCheckFieldAutocomplete();
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::notify_inlineBotRequesting(bool requesting) {
 | 
						|
	_attachEmoji.setLoading(requesting);
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::notify_replyMarkupUpdated(const HistoryItem *item) {
 | 
						|
	if (_keyboard.forMsgId() == item->fullId()) {
 | 
						|
		updateBotKeyboard(item->history(), true);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::notify_inlineKeyboardMoved(const HistoryItem *item, int oldKeyboardTop, int newKeyboardTop) {
 | 
						|
	if (_history == item->history() || _migrated == item->history()) {
 | 
						|
		if (int move = _list->moveScrollFollowingInlineKeyboard(item, oldKeyboardTop, newKeyboardTop)) {
 | 
						|
			_addToScroll = move;
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
bool HistoryWidget::notify_switchInlineBotButtonReceived(const QString &query, UserData *samePeerBot, MsgId samePeerReplyTo) {
 | 
						|
	if (samePeerBot) {
 | 
						|
		if (_history) {
 | 
						|
			TextWithTags textWithTags = { '@' + samePeerBot->username + ' ' + query, TextWithTags::Tags() };
 | 
						|
			MessageCursor cursor = { textWithTags.text.size(), textWithTags.text.size(), QFIXED_MAX };
 | 
						|
			auto replyTo = _history->peer->isUser() ? 0 : samePeerReplyTo;
 | 
						|
			_history->setLocalDraft(std_::make_unique<Data::Draft>(textWithTags, replyTo, cursor, false));
 | 
						|
			applyDraft();
 | 
						|
			return true;
 | 
						|
		}
 | 
						|
	} else if (auto bot = _peer ? _peer->asUser() : nullptr) {
 | 
						|
		PeerId toPeerId = bot->botInfo ? bot->botInfo->inlineReturnPeerId : 0;
 | 
						|
		if (!toPeerId) {
 | 
						|
			return false;
 | 
						|
		}
 | 
						|
		bot->botInfo->inlineReturnPeerId = 0;
 | 
						|
		History *h = App::history(toPeerId);
 | 
						|
		TextWithTags textWithTags = { '@' + bot->username + ' ' + query, TextWithTags::Tags() };
 | 
						|
		MessageCursor cursor = { textWithTags.text.size(), textWithTags.text.size(), QFIXED_MAX };
 | 
						|
		h->setLocalDraft(std_::make_unique<Data::Draft>(textWithTags, 0, cursor, false));
 | 
						|
		if (h == _history) {
 | 
						|
			applyDraft();
 | 
						|
		} else {
 | 
						|
			Ui::showPeerHistory(toPeerId, ShowAtUnreadMsgId);
 | 
						|
		}
 | 
						|
		return true;
 | 
						|
	}
 | 
						|
	return false;
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::notify_userIsBotChanged(UserData *user) {
 | 
						|
	if (_peer && _peer == user) {
 | 
						|
		_list->notifyIsBotChanged();
 | 
						|
		_list->updateBotInfo();
 | 
						|
		updateControlsVisibility();
 | 
						|
		updateControlsGeometry();
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::notify_migrateUpdated(PeerData *peer) {
 | 
						|
	if (_peer) {
 | 
						|
		if (_peer == peer) {
 | 
						|
			if (peer->migrateTo()) {
 | 
						|
				showHistory(peer->migrateTo()->id, (_showAtMsgId > 0) ? (-_showAtMsgId) : _showAtMsgId, true);
 | 
						|
			} else if ((_migrated ? _migrated->peer : 0) != peer->migrateFrom()) {
 | 
						|
				History *migrated = peer->migrateFrom() ? App::history(peer->migrateFrom()->id) : 0;
 | 
						|
				if (_migrated || (migrated && migrated->unreadCount() > 0)) {
 | 
						|
					showHistory(peer->id, peer->migrateFrom() ? _showAtMsgId : ((_showAtMsgId < 0 && -_showAtMsgId < ServerMaxMsgId) ? ShowAtUnreadMsgId : _showAtMsgId), true);
 | 
						|
				} else {
 | 
						|
					_migrated = migrated;
 | 
						|
					_list->notifyMigrateUpdated();
 | 
						|
					updateListSize();
 | 
						|
				}
 | 
						|
			}
 | 
						|
		} else if (_migrated && _migrated->peer == peer && peer->migrateTo() != _peer) {
 | 
						|
			showHistory(_peer->id, _showAtMsgId, true);
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::notify_clipStopperHidden(ClipStopperType type) {
 | 
						|
	if (_list) _list->update();
 | 
						|
}
 | 
						|
 | 
						|
bool HistoryWidget::cmd_search() {
 | 
						|
	if (!inFocusChain() || !_peer) return false;
 | 
						|
 | 
						|
	App::main()->searchInPeer(_peer);
 | 
						|
	return true;
 | 
						|
}
 | 
						|
 | 
						|
bool HistoryWidget::cmd_next_chat() {
 | 
						|
	PeerData *p = 0;
 | 
						|
	MsgId m = 0;
 | 
						|
	App::main()->peerAfter(_peer, qMax(_showAtMsgId, 0), p, m);
 | 
						|
	if (p) {
 | 
						|
		Ui::showPeerHistory(p, m);
 | 
						|
		return true;
 | 
						|
	}
 | 
						|
	return false;
 | 
						|
}
 | 
						|
 | 
						|
bool HistoryWidget::cmd_previous_chat() {
 | 
						|
	PeerData *p = 0;
 | 
						|
	MsgId m = 0;
 | 
						|
	App::main()->peerBefore(_peer, qMax(_showAtMsgId, 0), p, m);
 | 
						|
	if (p) {
 | 
						|
		Ui::showPeerHistory(p, m);
 | 
						|
		return true;
 | 
						|
	}
 | 
						|
	return false;
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::stickersGot(const MTPmessages_AllStickers &stickers) {
 | 
						|
	Global::SetLastStickersUpdate(getms(true));
 | 
						|
	_stickersUpdateRequest = 0;
 | 
						|
 | 
						|
	if (stickers.type() != mtpc_messages_allStickers) return;
 | 
						|
	const auto &d(stickers.c_messages_allStickers());
 | 
						|
 | 
						|
	const auto &d_sets(d.vsets.c_vector().v);
 | 
						|
 | 
						|
	auto &setsOrder = Global::RefStickerSetsOrder();
 | 
						|
	setsOrder.clear();
 | 
						|
 | 
						|
	auto &sets = Global::RefStickerSets();
 | 
						|
	QMap<uint64, uint64> setsToRequest;
 | 
						|
	for (auto &set : sets) {
 | 
						|
		if (!(set.flags & MTPDstickerSet::Flag::f_archived)) {
 | 
						|
			set.flags &= ~MTPDstickerSet::Flag::f_installed; // mark for removing
 | 
						|
		}
 | 
						|
	}
 | 
						|
	for_const (auto &setData, d_sets) {
 | 
						|
		if (setData.type() == mtpc_stickerSet) {
 | 
						|
			auto set = Stickers::feedSet(setData.c_stickerSet());
 | 
						|
			if (!(set->flags & MTPDstickerSet::Flag::f_archived) || (set->flags & MTPDstickerSet::Flag::f_official)) {
 | 
						|
				setsOrder.push_back(set->id);
 | 
						|
				if (set->stickers.isEmpty() || (set->flags & MTPDstickerSet_ClientFlag::f_not_loaded)) {
 | 
						|
					setsToRequest.insert(set->id, set->access);
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	bool writeRecent = false;
 | 
						|
	RecentStickerPack &recent(cGetRecentStickers());
 | 
						|
	for (Stickers::Sets::iterator it = sets.begin(), e = sets.end(); it != e;) {
 | 
						|
		bool installed = (it->flags & MTPDstickerSet::Flag::f_installed);
 | 
						|
		bool featured = (it->flags & MTPDstickerSet_ClientFlag::f_featured);
 | 
						|
		bool special = (it->flags & MTPDstickerSet_ClientFlag::f_special);
 | 
						|
		bool archived = (it->flags & MTPDstickerSet::Flag::f_archived);
 | 
						|
		if (!installed) { // remove not mine sets from recent stickers
 | 
						|
			for (RecentStickerPack::iterator i = recent.begin(); i != recent.cend();) {
 | 
						|
				if (it->stickers.indexOf(i->first) >= 0) {
 | 
						|
					i = recent.erase(i);
 | 
						|
					writeRecent = true;
 | 
						|
				} else {
 | 
						|
					++i;
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
		if (installed || featured || special || archived) {
 | 
						|
			++it;
 | 
						|
		} else {
 | 
						|
			it = sets.erase(it);
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if (Local::countStickersHash() != d.vhash.v) {
 | 
						|
		LOG(("API Error: received stickers hash %1 while counted hash is %2").arg(d.vhash.v).arg(Local::countStickersHash()));
 | 
						|
	}
 | 
						|
 | 
						|
	if (!setsToRequest.isEmpty() && App::api()) {
 | 
						|
		for (QMap<uint64, uint64>::const_iterator i = setsToRequest.cbegin(), e = setsToRequest.cend(); i != e; ++i) {
 | 
						|
			App::api()->scheduleStickerSetRequest(i.key(), i.value());
 | 
						|
		}
 | 
						|
		App::api()->requestStickerSets();
 | 
						|
	}
 | 
						|
 | 
						|
	Local::writeInstalledStickers();
 | 
						|
	if (writeRecent) Local::writeUserSettings();
 | 
						|
 | 
						|
	if (App::main()) emit App::main()->stickersUpdated();
 | 
						|
}
 | 
						|
 | 
						|
bool HistoryWidget::stickersFailed(const RPCError &error) {
 | 
						|
	if (MTP::isDefaultHandledError(error)) return false;
 | 
						|
 | 
						|
	LOG(("App Fail: Failed to get stickers!"));
 | 
						|
 | 
						|
	Global::SetLastStickersUpdate(getms(true));
 | 
						|
	_stickersUpdateRequest = 0;
 | 
						|
	return true;
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::recentStickersGot(const MTPmessages_RecentStickers &stickers) {
 | 
						|
	Global::SetLastRecentStickersUpdate(getms(true));
 | 
						|
	_recentStickersUpdateRequest = 0;
 | 
						|
 | 
						|
	if (stickers.type() != mtpc_messages_recentStickers) return;
 | 
						|
	auto &d = stickers.c_messages_recentStickers();
 | 
						|
 | 
						|
	auto &sets = Global::RefStickerSets();
 | 
						|
	auto it = sets.find(Stickers::CloudRecentSetId);
 | 
						|
 | 
						|
	auto &d_docs = d.vstickers.c_vector().v;
 | 
						|
	if (d_docs.isEmpty()) {
 | 
						|
		if (it != sets.cend()) {
 | 
						|
			sets.erase(it);
 | 
						|
		}
 | 
						|
	} else {
 | 
						|
		if (it == sets.cend()) {
 | 
						|
			it = sets.insert(Stickers::CloudRecentSetId, Stickers::Set(Stickers::CloudRecentSetId, 0, lang(lng_recent_stickers), QString(), 0, 0, qFlags(MTPDstickerSet_ClientFlag::f_special)));
 | 
						|
		} else {
 | 
						|
			it->title = lang(lng_recent_stickers);
 | 
						|
		}
 | 
						|
		it->hash = d.vhash.v;
 | 
						|
 | 
						|
		auto custom = sets.find(Stickers::CustomSetId);
 | 
						|
 | 
						|
		StickerPack pack;
 | 
						|
		pack.reserve(d_docs.size());
 | 
						|
		for (int32 i = 0, l = d_docs.size(); i != l; ++i) {
 | 
						|
			DocumentData *doc = App::feedDocument(d_docs.at(i));
 | 
						|
			if (!doc || !doc->sticker()) continue;
 | 
						|
 | 
						|
			pack.push_back(doc);
 | 
						|
			if (custom != sets.cend()) {
 | 
						|
				int32 index = custom->stickers.indexOf(doc);
 | 
						|
				if (index >= 0) {
 | 
						|
					custom->stickers.removeAt(index);
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
		if (custom != sets.cend() && custom->stickers.isEmpty()) {
 | 
						|
			sets.erase(custom);
 | 
						|
			custom = sets.end();
 | 
						|
		}
 | 
						|
 | 
						|
		bool writeRecent = false;
 | 
						|
		RecentStickerPack &recent(cGetRecentStickers());
 | 
						|
		for (RecentStickerPack::iterator i = recent.begin(); i != recent.cend();) {
 | 
						|
			if (it->stickers.indexOf(i->first) >= 0 && pack.indexOf(i->first) < 0) {
 | 
						|
				i = recent.erase(i);
 | 
						|
				writeRecent = true;
 | 
						|
			} else {
 | 
						|
				++i;
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		if (pack.isEmpty()) {
 | 
						|
			sets.erase(it);
 | 
						|
		} else {
 | 
						|
			it->stickers = pack;
 | 
						|
			it->emoji.clear();
 | 
						|
		}
 | 
						|
 | 
						|
		if (writeRecent) {
 | 
						|
			Local::writeUserSettings();
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if (Local::countRecentStickersHash() != d.vhash.v) {
 | 
						|
		LOG(("API Error: received stickers hash %1 while counted hash is %2").arg(d.vhash.v).arg(Local::countRecentStickersHash()));
 | 
						|
	}
 | 
						|
 | 
						|
	Local::writeRecentStickers();
 | 
						|
 | 
						|
	if (App::main()) emit App::main()->stickersUpdated();
 | 
						|
}
 | 
						|
 | 
						|
bool HistoryWidget::recentStickersFailed(const RPCError &error) {
 | 
						|
	if (MTP::isDefaultHandledError(error)) return false;
 | 
						|
 | 
						|
	LOG(("App Fail: Failed to get recent stickers!"));
 | 
						|
 | 
						|
	Global::SetLastRecentStickersUpdate(getms(true));
 | 
						|
	_recentStickersUpdateRequest = 0;
 | 
						|
	return true;
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::featuredStickersGot(const MTPmessages_FeaturedStickers &stickers) {
 | 
						|
	Global::SetLastFeaturedStickersUpdate(getms(true));
 | 
						|
	_featuredStickersUpdateRequest = 0;
 | 
						|
 | 
						|
	if (stickers.type() != mtpc_messages_featuredStickers) return;
 | 
						|
	auto &d = stickers.c_messages_featuredStickers();
 | 
						|
 | 
						|
	OrderedSet<uint64> unread;
 | 
						|
	for_const (auto &unreadSetId, d.vunread.c_vector().v) {
 | 
						|
		unread.insert(unreadSetId.v);
 | 
						|
	}
 | 
						|
 | 
						|
	auto &d_sets = d.vsets.c_vector().v;
 | 
						|
 | 
						|
	auto &setsOrder = Global::RefFeaturedStickerSetsOrder();
 | 
						|
	setsOrder.clear();
 | 
						|
 | 
						|
	auto &sets = Global::RefStickerSets();
 | 
						|
	QMap<uint64, uint64> setsToRequest;
 | 
						|
	for (auto &set : sets) {
 | 
						|
		set.flags &= ~MTPDstickerSet_ClientFlag::f_featured; // mark for removing
 | 
						|
	}
 | 
						|
	for (int i = 0, l = d_sets.size(); i != l; ++i) {
 | 
						|
		auto &setData = d_sets[i];
 | 
						|
		const MTPDstickerSet *set = nullptr;
 | 
						|
		switch (setData.type()) {
 | 
						|
		case mtpc_stickerSetCovered: {
 | 
						|
			auto &d = setData.c_stickerSetCovered();
 | 
						|
			if (d.vset.type() == mtpc_stickerSet) {
 | 
						|
				set = &d.vset.c_stickerSet();
 | 
						|
			}
 | 
						|
		} break;
 | 
						|
		case mtpc_stickerSetMultiCovered: {
 | 
						|
			auto &d = setData.c_stickerSetMultiCovered();
 | 
						|
			if (d.vset.type() == mtpc_stickerSet) {
 | 
						|
				set = &d.vset.c_stickerSet();
 | 
						|
			}
 | 
						|
		} break;
 | 
						|
		}
 | 
						|
 | 
						|
		if (set) {
 | 
						|
			auto it = sets.find(set->vid.v);
 | 
						|
			auto title = stickerSetTitle(*set);
 | 
						|
			if (it == sets.cend()) {
 | 
						|
				auto setClientFlags = MTPDstickerSet_ClientFlag::f_featured | MTPDstickerSet_ClientFlag::f_not_loaded;
 | 
						|
				if (unread.contains(set->vid.v)) {
 | 
						|
					setClientFlags |= MTPDstickerSet_ClientFlag::f_unread;
 | 
						|
				}
 | 
						|
				it = sets.insert(set->vid.v, Stickers::Set(set->vid.v, set->vaccess_hash.v, title, qs(set->vshort_name), set->vcount.v, set->vhash.v, set->vflags.v | setClientFlags));
 | 
						|
			} else {
 | 
						|
				it->access = set->vaccess_hash.v;
 | 
						|
				it->title = title;
 | 
						|
				it->shortName = qs(set->vshort_name);
 | 
						|
				auto clientFlags = it->flags & (MTPDstickerSet_ClientFlag::f_featured | MTPDstickerSet_ClientFlag::f_unread | MTPDstickerSet_ClientFlag::f_not_loaded | MTPDstickerSet_ClientFlag::f_special);
 | 
						|
				it->flags = set->vflags.v | clientFlags;
 | 
						|
				it->flags |= MTPDstickerSet_ClientFlag::f_featured;
 | 
						|
				if (unread.contains(it->id)) {
 | 
						|
					it->flags |= MTPDstickerSet_ClientFlag::f_unread;
 | 
						|
				} else {
 | 
						|
					it->flags &= ~MTPDstickerSet_ClientFlag::f_unread;
 | 
						|
				}
 | 
						|
				if (it->count != set->vcount.v || it->hash != set->vhash.v || it->emoji.isEmpty()) {
 | 
						|
					it->count = set->vcount.v;
 | 
						|
					it->hash = set->vhash.v;
 | 
						|
					it->flags |= MTPDstickerSet_ClientFlag::f_not_loaded; // need to request this set
 | 
						|
				}
 | 
						|
			}
 | 
						|
			setsOrder.push_back(set->vid.v);
 | 
						|
			if (it->stickers.isEmpty() || (it->flags & MTPDstickerSet_ClientFlag::f_not_loaded)) {
 | 
						|
				setsToRequest.insert(set->vid.v, set->vaccess_hash.v);
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	int unreadCount = 0;
 | 
						|
	for (auto it = sets.begin(), e = sets.end(); it != e;) {
 | 
						|
		bool installed = (it->flags & MTPDstickerSet::Flag::f_installed);
 | 
						|
		bool featured = (it->flags & MTPDstickerSet_ClientFlag::f_featured);
 | 
						|
		bool special = (it->flags & MTPDstickerSet_ClientFlag::f_special);
 | 
						|
		bool archived = (it->flags & MTPDstickerSet::Flag::f_archived);
 | 
						|
		if (installed || featured || special || archived) {
 | 
						|
			if (featured && (it->flags & MTPDstickerSet_ClientFlag::f_unread)) {
 | 
						|
				++unreadCount;
 | 
						|
			}
 | 
						|
			++it;
 | 
						|
		} else {
 | 
						|
			it = sets.erase(it);
 | 
						|
		}
 | 
						|
	}
 | 
						|
	Global::SetFeaturedStickerSetsUnreadCount(unreadCount);
 | 
						|
 | 
						|
	if (Local::countFeaturedStickersHash() != d.vhash.v) {
 | 
						|
		LOG(("API Error: received featured stickers hash %1 while counted hash is %2").arg(d.vhash.v).arg(Local::countFeaturedStickersHash()));
 | 
						|
	}
 | 
						|
 | 
						|
	if (!setsToRequest.isEmpty() && App::api()) {
 | 
						|
		for (QMap<uint64, uint64>::const_iterator i = setsToRequest.cbegin(), e = setsToRequest.cend(); i != e; ++i) {
 | 
						|
			App::api()->scheduleStickerSetRequest(i.key(), i.value());
 | 
						|
		}
 | 
						|
		App::api()->requestStickerSets();
 | 
						|
	}
 | 
						|
 | 
						|
	Local::writeFeaturedStickers();
 | 
						|
 | 
						|
	if (App::main()) emit App::main()->stickersUpdated();
 | 
						|
}
 | 
						|
 | 
						|
bool HistoryWidget::featuredStickersFailed(const RPCError &error) {
 | 
						|
	if (MTP::isDefaultHandledError(error)) return false;
 | 
						|
 | 
						|
	LOG(("App Fail: Failed to get featured stickers!"));
 | 
						|
 | 
						|
	Global::SetLastFeaturedStickersUpdate(getms(true));
 | 
						|
	_featuredStickersUpdateRequest = 0;
 | 
						|
	return true;
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::savedGifsGot(const MTPmessages_SavedGifs &gifs) {
 | 
						|
	cSetLastSavedGifsUpdate(getms(true));
 | 
						|
	_savedGifsUpdateRequest = 0;
 | 
						|
 | 
						|
	if (gifs.type() != mtpc_messages_savedGifs) return;
 | 
						|
	const auto &d(gifs.c_messages_savedGifs());
 | 
						|
 | 
						|
	const auto &d_gifs(d.vgifs.c_vector().v);
 | 
						|
 | 
						|
	SavedGifs &saved(cRefSavedGifs());
 | 
						|
	saved.clear();
 | 
						|
 | 
						|
	saved.reserve(d_gifs.size());
 | 
						|
	for (int32 i = 0, l = d_gifs.size(); i != l; ++i) {
 | 
						|
		DocumentData *doc = App::feedDocument(d_gifs.at(i));
 | 
						|
		if (!doc || !doc->isAnimation()) {
 | 
						|
			LOG(("API Error: bad document returned in HistoryWidget::savedGifsGot!"));
 | 
						|
			continue;
 | 
						|
		}
 | 
						|
 | 
						|
		saved.push_back(doc);
 | 
						|
	}
 | 
						|
	if (Local::countSavedGifsHash() != d.vhash.v) {
 | 
						|
		LOG(("API Error: received saved gifs hash %1 while counted hash is %2").arg(d.vhash.v).arg(Local::countSavedGifsHash()));
 | 
						|
	}
 | 
						|
 | 
						|
	Local::writeSavedGifs();
 | 
						|
 | 
						|
	if (App::main()) emit App::main()->savedGifsUpdated();
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::saveGif(DocumentData *doc) {
 | 
						|
	if (doc->isGifv() && cSavedGifs().indexOf(doc) != 0) {
 | 
						|
		MTPInputDocument mtpInput = doc->mtpInput();
 | 
						|
		if (mtpInput.type() != mtpc_inputDocumentEmpty) {
 | 
						|
			MTP::send(MTPmessages_SaveGif(mtpInput, MTP_bool(false)), rpcDone(&HistoryWidget::saveGifDone, doc));
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::saveGifDone(DocumentData *doc, const MTPBool &result) {
 | 
						|
	if (mtpIsTrue(result)) {
 | 
						|
		App::addSavedGif(doc);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
bool HistoryWidget::savedGifsFailed(const RPCError &error) {
 | 
						|
	if (MTP::isDefaultHandledError(error)) return false;
 | 
						|
 | 
						|
	LOG(("App Fail: Failed to get saved gifs!"));
 | 
						|
 | 
						|
	cSetLastSavedGifsUpdate(getms(true));
 | 
						|
	_savedGifsUpdateRequest = 0;
 | 
						|
	return true;
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::clearReplyReturns() {
 | 
						|
	_replyReturns.clear();
 | 
						|
	_replyReturn = 0;
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::pushReplyReturn(HistoryItem *item) {
 | 
						|
	if (!item) return;
 | 
						|
	if (item->history() == _history) {
 | 
						|
		_replyReturns.push_back(item->id);
 | 
						|
	} else if (item->history() == _migrated) {
 | 
						|
		_replyReturns.push_back(-item->id);
 | 
						|
	} else {
 | 
						|
		return;
 | 
						|
	}
 | 
						|
	_replyReturn = item;
 | 
						|
	updateControlsVisibility();
 | 
						|
}
 | 
						|
 | 
						|
QList<MsgId> HistoryWidget::replyReturns() {
 | 
						|
	return _replyReturns;
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::setReplyReturns(PeerId peer, const QList<MsgId> &replyReturns) {
 | 
						|
	if (!_peer || _peer->id != peer) return;
 | 
						|
 | 
						|
	_replyReturns = replyReturns;
 | 
						|
	if (_replyReturns.isEmpty()) {
 | 
						|
		_replyReturn = 0;
 | 
						|
	} else if (_replyReturns.back() < 0 && -_replyReturns.back() < ServerMaxMsgId) {
 | 
						|
		_replyReturn = App::histItemById(0, -_replyReturns.back());
 | 
						|
	} else {
 | 
						|
		_replyReturn = App::histItemById(_channel, _replyReturns.back());
 | 
						|
	}
 | 
						|
	while (!_replyReturns.isEmpty() && !_replyReturn) {
 | 
						|
		_replyReturns.pop_back();
 | 
						|
		if (_replyReturns.isEmpty()) {
 | 
						|
			_replyReturn = 0;
 | 
						|
		} else if (_replyReturns.back() < 0 && -_replyReturns.back() < ServerMaxMsgId) {
 | 
						|
			_replyReturn = App::histItemById(0, -_replyReturns.back());
 | 
						|
		} else {
 | 
						|
			_replyReturn = App::histItemById(_channel, _replyReturns.back());
 | 
						|
		}
 | 
						|
	}
 | 
						|
	updateControlsVisibility();
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::calcNextReplyReturn() {
 | 
						|
	_replyReturn = 0;
 | 
						|
	while (!_replyReturns.isEmpty() && !_replyReturn) {
 | 
						|
		_replyReturns.pop_back();
 | 
						|
		if (_replyReturns.isEmpty()) {
 | 
						|
			_replyReturn = 0;
 | 
						|
		} else if (_replyReturns.back() < 0 && -_replyReturns.back() < ServerMaxMsgId) {
 | 
						|
			_replyReturn = App::histItemById(0, -_replyReturns.back());
 | 
						|
		} else {
 | 
						|
			_replyReturn = App::histItemById(_channel, _replyReturns.back());
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if (!_replyReturn) updateControlsVisibility();
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::fastShowAtEnd(History *h) {
 | 
						|
	if (h == _history) {
 | 
						|
		h->getReadyFor(ShowAtTheEndMsgId);
 | 
						|
 | 
						|
		clearAllLoadRequests();
 | 
						|
 | 
						|
		setMsgId(ShowAtUnreadMsgId);
 | 
						|
		_histInited = false;
 | 
						|
 | 
						|
		if (h->isReadyFor(_showAtMsgId)) {
 | 
						|
			historyLoaded();
 | 
						|
		} else {
 | 
						|
			firstLoadMessages();
 | 
						|
			doneShow();
 | 
						|
		}
 | 
						|
	} else if (h) {
 | 
						|
		h->getReadyFor(ShowAtTheEndMsgId);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::applyDraft(bool parseLinks) {
 | 
						|
	auto draft = _history ? _history->draft() : nullptr;
 | 
						|
	if (!draft || !canWriteMessage()) {
 | 
						|
		clearFieldText();
 | 
						|
		_field.setFocus();
 | 
						|
		_replyEditMsg = nullptr;
 | 
						|
		_editMsgId = _replyToId = 0;
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	_textUpdateEvents = 0;
 | 
						|
	setFieldText(draft->textWithTags);
 | 
						|
	_field.setFocus();
 | 
						|
	draft->cursor.applyTo(_field);
 | 
						|
	_textUpdateEvents = TextUpdateEvent::SaveDraft | TextUpdateEvent::SendTyping;
 | 
						|
	_previewCancelled = draft->previewCancelled;
 | 
						|
	_replyEditMsg = nullptr;
 | 
						|
	if (auto editDraft = _history->editDraft()) {
 | 
						|
		_editMsgId = editDraft->msgId;
 | 
						|
		_replyToId = 0;
 | 
						|
	} else {
 | 
						|
		_editMsgId = 0;
 | 
						|
		_replyToId = readyToForward() ? 0 : _history->localDraft()->msgId;
 | 
						|
	}
 | 
						|
 | 
						|
	if (parseLinks) {
 | 
						|
		onPreviewParse();
 | 
						|
	}
 | 
						|
	if (_editMsgId || _replyToId) {
 | 
						|
		updateReplyEditTexts();
 | 
						|
		if (!_replyEditMsg && App::api()) {
 | 
						|
			App::api()->requestMessageData(_peer->asChannel(), _editMsgId ? _editMsgId : _replyToId, replyEditMessageDataCallback());
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::applyCloudDraft(History *history) {
 | 
						|
	if (_history == history && !_editMsgId) {
 | 
						|
		applyDraft();
 | 
						|
 | 
						|
		updateControlsVisibility();
 | 
						|
		updateControlsGeometry();
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::showHistory(const PeerId &peerId, MsgId showAtMsgId, bool reload) {
 | 
						|
	MsgId wasMsgId = _showAtMsgId;
 | 
						|
	History *wasHistory = _history;
 | 
						|
 | 
						|
	bool startBot = (showAtMsgId == ShowAndStartBotMsgId);
 | 
						|
	if (startBot) {
 | 
						|
		showAtMsgId = ShowAtTheEndMsgId;
 | 
						|
	}
 | 
						|
 | 
						|
	if (_history) {
 | 
						|
		if (_peer->id == peerId && !reload) {
 | 
						|
			bool canShowNow = _history->isReadyFor(showAtMsgId);
 | 
						|
			if (!canShowNow) {
 | 
						|
				delayedShowAt(showAtMsgId);
 | 
						|
			} else {
 | 
						|
				_history->forgetScrollState();
 | 
						|
				if (_migrated) {
 | 
						|
					_migrated->forgetScrollState();
 | 
						|
				}
 | 
						|
 | 
						|
				clearDelayedShowAt();
 | 
						|
				if (_replyReturn) {
 | 
						|
					if (_replyReturn->history() == _history && _replyReturn->id == showAtMsgId) {
 | 
						|
						calcNextReplyReturn();
 | 
						|
					} else if (_replyReturn->history() == _migrated && -_replyReturn->id == showAtMsgId) {
 | 
						|
						calcNextReplyReturn();
 | 
						|
					}
 | 
						|
				}
 | 
						|
 | 
						|
				_showAtMsgId = showAtMsgId;
 | 
						|
				_histInited = false;
 | 
						|
 | 
						|
				historyLoaded();
 | 
						|
			}
 | 
						|
			App::main()->dlgUpdated(wasHistory, wasMsgId);
 | 
						|
			emit historyShown(_history, _showAtMsgId);
 | 
						|
 | 
						|
			App::main()->topBar()->update();
 | 
						|
			update();
 | 
						|
 | 
						|
			if (startBot && _peer->isUser() && _peer->asUser()->botInfo) {
 | 
						|
				if (wasHistory) _peer->asUser()->botInfo->inlineReturnPeerId = wasHistory->peer->id;
 | 
						|
				onBotStart();
 | 
						|
				_history->clearLocalDraft();
 | 
						|
				applyDraft();
 | 
						|
			}
 | 
						|
			return;
 | 
						|
		}
 | 
						|
		if (_history->mySendActions.contains(SendActionTyping)) {
 | 
						|
			updateSendAction(_history, SendActionTyping, -1);
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if (!cAutoPlayGif()) {
 | 
						|
		App::stopGifItems();
 | 
						|
	}
 | 
						|
	clearReplyReturns();
 | 
						|
 | 
						|
	clearAllLoadRequests();
 | 
						|
 | 
						|
	if (_history) {
 | 
						|
		if (App::main()) App::main()->saveDraftToCloud();
 | 
						|
		if (_migrated) {
 | 
						|
			_migrated->clearLocalDraft(); // use migrated draft only once
 | 
						|
			_migrated->clearEditDraft();
 | 
						|
		}
 | 
						|
 | 
						|
		_history->showAtMsgId = _showAtMsgId;
 | 
						|
 | 
						|
		destroyUnreadBar();
 | 
						|
		destroyPinnedBar();
 | 
						|
		_history = _migrated = nullptr;
 | 
						|
		updateBotKeyboard();
 | 
						|
	}
 | 
						|
 | 
						|
	App::clearMousedItems();
 | 
						|
 | 
						|
	_addToScroll = 0;
 | 
						|
	_saveEditMsgRequestId = 0;
 | 
						|
	_replyEditMsg = nullptr;
 | 
						|
	_editMsgId = _replyToId = 0;
 | 
						|
	_previewData = 0;
 | 
						|
	_previewCache.clear();
 | 
						|
	_fieldBarCancel.hide();
 | 
						|
 | 
						|
	_membersDropdownShowTimer.stop();
 | 
						|
	if (_list) _list->deleteLater();
 | 
						|
	_list = nullptr;
 | 
						|
	_scroll.takeWidget();
 | 
						|
	updateTopBarSelection();
 | 
						|
 | 
						|
	clearInlineBot();
 | 
						|
 | 
						|
	_showAtMsgId = showAtMsgId;
 | 
						|
	_histInited = false;
 | 
						|
 | 
						|
	_peer = peerId ? App::peer(peerId) : nullptr;
 | 
						|
	_channel = _peer ? peerToChannel(_peer->id) : NoChannel;
 | 
						|
	_canSendMessages = canSendMessages(_peer);
 | 
						|
	if (_peer && _peer->isChannel()) {
 | 
						|
		_peer->asChannel()->updateFull();
 | 
						|
		_joinChannel.setText(lang(_peer->isMegagroup() ? lng_group_invite_join : lng_channel_join));
 | 
						|
	}
 | 
						|
 | 
						|
	_unblockRequest = _reportSpamRequest = 0;
 | 
						|
	if (_reportSpamSettingRequestId > 0) {
 | 
						|
		MTP::cancel(_reportSpamSettingRequestId);
 | 
						|
	}
 | 
						|
	_reportSpamSettingRequestId = ReportSpamRequestNeeded;
 | 
						|
 | 
						|
	_titlePeerText = QString();
 | 
						|
	_titlePeerTextWidth = 0;
 | 
						|
 | 
						|
	noSelectingScroll();
 | 
						|
	_selCount = 0;
 | 
						|
	App::main()->topBar()->showSelected(0);
 | 
						|
 | 
						|
	App::hoveredItem(nullptr);
 | 
						|
	App::pressedItem(nullptr);
 | 
						|
	App::hoveredLinkItem(nullptr);
 | 
						|
	App::pressedLinkItem(nullptr);
 | 
						|
	App::contextItem(nullptr);
 | 
						|
	App::mousedItem(nullptr);
 | 
						|
 | 
						|
	if (_peer) {
 | 
						|
		App::forgetMedia();
 | 
						|
		_serviceImageCacheSize = imageCacheSize();
 | 
						|
		MTP::clearLoaderPriorities();
 | 
						|
 | 
						|
		_history = App::history(_peer->id);
 | 
						|
		_migrated = _peer->migrateFrom() ? App::history(_peer->migrateFrom()->id) : 0;
 | 
						|
 | 
						|
		if (_channel) {
 | 
						|
			updateNotifySettings();
 | 
						|
			if (_peer->notify == UnknownNotifySettings) {
 | 
						|
				App::api()->requestNotifySetting(_peer);
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		if (_showAtMsgId == ShowAtUnreadMsgId) {
 | 
						|
			if (_history->scrollTopItem) {
 | 
						|
				_showAtMsgId = _history->showAtMsgId;
 | 
						|
			}
 | 
						|
		} else {
 | 
						|
			_history->forgetScrollState();
 | 
						|
			if (_migrated) {
 | 
						|
				_migrated->forgetScrollState();
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		_list = new HistoryInner(this, &_scroll, _history);
 | 
						|
		_list->hide();
 | 
						|
		_scroll.hide();
 | 
						|
		_scroll.setWidget(_list);
 | 
						|
		_list->show();
 | 
						|
 | 
						|
		_updateHistoryItems.stop();
 | 
						|
 | 
						|
		pinnedMsgVisibilityUpdated();
 | 
						|
		if (_history->scrollTopItem || (_migrated && _migrated->scrollTopItem) || _history->isReadyFor(_showAtMsgId)) {
 | 
						|
			historyLoaded();
 | 
						|
		} else {
 | 
						|
			firstLoadMessages();
 | 
						|
			doneShow();
 | 
						|
		}
 | 
						|
 | 
						|
		emit App::main()->peerUpdated(_peer);
 | 
						|
 | 
						|
		Local::readDraftsWithCursors(_history);
 | 
						|
		if (_migrated) {
 | 
						|
			Local::readDraftsWithCursors(_migrated);
 | 
						|
			_migrated->clearEditDraft();
 | 
						|
			_history->takeLocalDraft(_migrated);
 | 
						|
		}
 | 
						|
		applyDraft(false);
 | 
						|
 | 
						|
		resizeEvent(nullptr);
 | 
						|
		if (!_previewCancelled) {
 | 
						|
			onPreviewParse();
 | 
						|
		}
 | 
						|
 | 
						|
		connect(&_scroll, SIGNAL(geometryChanged()), _list, SLOT(onParentGeometryChanged()));
 | 
						|
 | 
						|
		if (startBot && _peer->isUser() && _peer->asUser()->botInfo) {
 | 
						|
			if (wasHistory) _peer->asUser()->botInfo->inlineReturnPeerId = wasHistory->peer->id;
 | 
						|
			onBotStart();
 | 
						|
		}
 | 
						|
		unreadCountChanged(_history); // set _historyToEnd badge.
 | 
						|
	} else {
 | 
						|
		clearFieldText();
 | 
						|
		doneShow();
 | 
						|
	}
 | 
						|
 | 
						|
	if (App::wnd()) QTimer::singleShot(0, App::wnd(), SLOT(setInnerFocus()));
 | 
						|
 | 
						|
	App::main()->dlgUpdated(wasHistory, wasMsgId);
 | 
						|
	emit historyShown(_history, _showAtMsgId);
 | 
						|
 | 
						|
	App::main()->topBar()->update();
 | 
						|
	update();
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::clearDelayedShowAt() {
 | 
						|
	_delayedShowAtMsgId = -1;
 | 
						|
	if (_delayedShowAtRequest) {
 | 
						|
		MTP::cancel(_delayedShowAtRequest);
 | 
						|
		_delayedShowAtRequest = 0;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::clearAllLoadRequests() {
 | 
						|
	clearDelayedShowAt();
 | 
						|
	if (_firstLoadRequest) MTP::cancel(_firstLoadRequest);
 | 
						|
	if (_preloadRequest) MTP::cancel(_preloadRequest);
 | 
						|
	if (_preloadDownRequest) MTP::cancel(_preloadDownRequest);
 | 
						|
	_preloadRequest = _preloadDownRequest = _firstLoadRequest = 0;
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::contactsReceived() {
 | 
						|
	if (!_peer) return;
 | 
						|
	updateReportSpamStatus();
 | 
						|
	updateControlsVisibility();
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::updateAfterDrag() {
 | 
						|
	if (_list) _list->dragActionUpdate(QCursor::pos());
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::updateFieldSubmitSettings() {
 | 
						|
	FlatTextarea::SubmitSettings settings = FlatTextarea::SubmitSettings::Enter;
 | 
						|
	if (_inlineBotCancel) {
 | 
						|
		settings = FlatTextarea::SubmitSettings::None;
 | 
						|
	} else if (cCtrlEnter()) {
 | 
						|
		settings = FlatTextarea::SubmitSettings::CtrlEnter;
 | 
						|
	}
 | 
						|
	_field.setSubmitSettings(settings);
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::updateNotifySettings() {
 | 
						|
	if (!_peer || !_peer->isChannel()) return;
 | 
						|
 | 
						|
	_muteUnmute.setText(lang(_history->mute() ? lng_channel_unmute : lng_channel_mute));
 | 
						|
	if (_peer->notify != UnknownNotifySettings) {
 | 
						|
		_silent.setChecked(_peer->notify != EmptyNotifySettings && (_peer->notify->flags & MTPDpeerNotifySettings::Flag::f_silent));
 | 
						|
		if (_silent.isHidden() && hasSilentToggle()) {
 | 
						|
			updateControlsVisibility();
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
bool HistoryWidget::contentOverlapped(const QRect &globalRect) {
 | 
						|
	return (_attachDragDocument->overlaps(globalRect) ||
 | 
						|
			_attachDragPhoto->overlaps(globalRect) ||
 | 
						|
			_attachType->overlaps(globalRect) ||
 | 
						|
			_fieldAutocomplete->overlaps(globalRect) ||
 | 
						|
			_emojiPan->overlaps(globalRect));
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::updateReportSpamStatus() {
 | 
						|
	if (!_peer || (_peer->isUser() && (peerToUser(_peer->id) == MTP::authedId() || isNotificationsUser(_peer->id) || isServiceUser(_peer->id) || _peer->asUser()->botInfo))) {
 | 
						|
		_reportSpamStatus = dbiprsHidden;
 | 
						|
		return;
 | 
						|
	} else if (!_firstLoadRequest && _history->isEmpty()) {
 | 
						|
		_reportSpamStatus = dbiprsNoButton;
 | 
						|
		if (cReportSpamStatuses().contains(_peer->id)) {
 | 
						|
			cRefReportSpamStatuses().remove(_peer->id);
 | 
						|
			Local::writeReportSpamStatuses();
 | 
						|
		}
 | 
						|
		return;
 | 
						|
	} else {
 | 
						|
		ReportSpamStatuses::const_iterator i = cReportSpamStatuses().constFind(_peer->id);
 | 
						|
		if (i != cReportSpamStatuses().cend()) {
 | 
						|
			_reportSpamStatus = i.value();
 | 
						|
			if (_reportSpamStatus == dbiprsNoButton) {
 | 
						|
				_reportSpamStatus = dbiprsHidden;
 | 
						|
				if (!_peer->isUser() || _peer->asUser()->contact < 1) {
 | 
						|
					MTP::send(MTPmessages_HideReportSpam(_peer->input));
 | 
						|
				}
 | 
						|
 | 
						|
				cRefReportSpamStatuses().insert(_peer->id, _reportSpamStatus);
 | 
						|
				Local::writeReportSpamStatuses();
 | 
						|
			} else if (_reportSpamStatus == dbiprsShowButton) {
 | 
						|
				requestReportSpamSetting();
 | 
						|
			}
 | 
						|
			_reportSpamPanel.setReported(_reportSpamStatus == dbiprsReportSent, _peer);
 | 
						|
			return;
 | 
						|
		} else if (_peer->migrateFrom()) { // migrate report status
 | 
						|
			i = cReportSpamStatuses().constFind(_peer->migrateFrom()->id);
 | 
						|
			if (i != cReportSpamStatuses().cend()) {
 | 
						|
				_reportSpamStatus = i.value();
 | 
						|
				if (_reportSpamStatus == dbiprsNoButton) {
 | 
						|
					_reportSpamStatus = dbiprsHidden;
 | 
						|
					if (!_peer->isUser() || _peer->asUser()->contact < 1) {
 | 
						|
						MTP::send(MTPmessages_HideReportSpam(_peer->input));
 | 
						|
					}
 | 
						|
				} else if (_reportSpamStatus == dbiprsShowButton) {
 | 
						|
					requestReportSpamSetting();
 | 
						|
				}
 | 
						|
				cRefReportSpamStatuses().insert(_peer->id, _reportSpamStatus);
 | 
						|
				Local::writeReportSpamStatuses();
 | 
						|
 | 
						|
				_reportSpamPanel.setReported(_reportSpamStatus == dbiprsReportSent, _peer);
 | 
						|
				return;
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if (!cContactsReceived() || _firstLoadRequest) {
 | 
						|
		_reportSpamStatus = dbiprsUnknown;
 | 
						|
	} else if (_peer->isUser() && _peer->asUser()->contact > 0) {
 | 
						|
		_reportSpamStatus = dbiprsHidden;
 | 
						|
	} else {
 | 
						|
		_reportSpamStatus = dbiprsRequesting;
 | 
						|
		requestReportSpamSetting();
 | 
						|
	}
 | 
						|
	if (_reportSpamStatus == dbiprsHidden) {
 | 
						|
		_reportSpamPanel.setReported(false, _peer);
 | 
						|
		cRefReportSpamStatuses().insert(_peer->id, _reportSpamStatus);
 | 
						|
		Local::writeReportSpamStatuses();
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::requestReportSpamSetting() {
 | 
						|
	if (_reportSpamSettingRequestId >= 0 || !_peer) return;
 | 
						|
 | 
						|
	_reportSpamSettingRequestId = MTP::send(MTPmessages_GetPeerSettings(_peer->input), rpcDone(&HistoryWidget::reportSpamSettingDone), rpcFail(&HistoryWidget::reportSpamSettingFail));
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::reportSpamSettingDone(const MTPPeerSettings &result, mtpRequestId req) {
 | 
						|
	if (req != _reportSpamSettingRequestId) return;
 | 
						|
 | 
						|
	_reportSpamSettingRequestId = 0;
 | 
						|
	if (result.type() == mtpc_peerSettings) {
 | 
						|
		const auto &d(result.c_peerSettings());
 | 
						|
		DBIPeerReportSpamStatus status = d.is_report_spam() ? dbiprsShowButton : dbiprsHidden;
 | 
						|
		if (status != _reportSpamStatus) {
 | 
						|
			_reportSpamStatus = status;
 | 
						|
			_reportSpamPanel.setReported(false, _peer);
 | 
						|
 | 
						|
			cRefReportSpamStatuses().insert(_peer->id, _reportSpamStatus);
 | 
						|
			Local::writeReportSpamStatuses();
 | 
						|
 | 
						|
			updateControlsVisibility();
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
bool HistoryWidget::reportSpamSettingFail(const RPCError &error, mtpRequestId req) {
 | 
						|
	if (MTP::isDefaultHandledError(error)) return false;
 | 
						|
 | 
						|
	if (req == _reportSpamSettingRequestId) {
 | 
						|
		req = 0;
 | 
						|
	}
 | 
						|
	return true;
 | 
						|
}
 | 
						|
 | 
						|
bool HistoryWidget::canWriteMessage() const {
 | 
						|
	if (!_history || _a_show.animating()) return false;
 | 
						|
	if (isBlocked() || isJoinChannel() || isMuteUnmute() || isBotStart()) return false;
 | 
						|
	if (!_canSendMessages) return false;
 | 
						|
	return true;
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::updateControlsVisibility() {
 | 
						|
	if (!_a_show.animating()) {
 | 
						|
		_topShadow->setVisible(_peer ? true : false);
 | 
						|
	}
 | 
						|
	updateToEndVisibility();
 | 
						|
	if (!_history || _a_show.animating()) {
 | 
						|
		_reportSpamPanel.hide();
 | 
						|
		_scroll.hide();
 | 
						|
		_kbScroll.hide();
 | 
						|
		_send.hide();
 | 
						|
		if (_inlineBotCancel) _inlineBotCancel->hide();
 | 
						|
		_unblock.hide();
 | 
						|
		_botStart.hide();
 | 
						|
		_joinChannel.hide();
 | 
						|
		_muteUnmute.hide();
 | 
						|
		_fieldAutocomplete->hide();
 | 
						|
		_field.hide();
 | 
						|
		_fieldBarCancel.hide();
 | 
						|
		_attachDocument.hide();
 | 
						|
		_attachPhoto.hide();
 | 
						|
		_attachEmoji.hide();
 | 
						|
		_silent.hide();
 | 
						|
		_historyToEnd->hide();
 | 
						|
		_kbShow.hide();
 | 
						|
		_kbHide.hide();
 | 
						|
		_cmdStart.hide();
 | 
						|
		_attachType->hide();
 | 
						|
		_emojiPan->hide();
 | 
						|
		if (_pinnedBar) {
 | 
						|
			_pinnedBar->cancel->hide();
 | 
						|
			_pinnedBar->shadow->hide();
 | 
						|
		}
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	if (_pinnedBar) {
 | 
						|
		_pinnedBar->cancel->show();
 | 
						|
		_pinnedBar->shadow->show();
 | 
						|
	}
 | 
						|
	if (_firstLoadRequest && !_scroll.isHidden()) {
 | 
						|
		_scroll.hide();
 | 
						|
	} else if (!_firstLoadRequest && _scroll.isHidden()) {
 | 
						|
		_scroll.show();
 | 
						|
	}
 | 
						|
	if (_reportSpamStatus == dbiprsShowButton || _reportSpamStatus == dbiprsReportSent) {
 | 
						|
		_reportSpamPanel.show();
 | 
						|
	} else {
 | 
						|
		_reportSpamPanel.hide();
 | 
						|
	}
 | 
						|
	if (isBlocked() || isJoinChannel() || isMuteUnmute()) {
 | 
						|
		if (isBlocked()) {
 | 
						|
			_joinChannel.hide();
 | 
						|
			_muteUnmute.hide();
 | 
						|
			if (_unblock.isHidden()) {
 | 
						|
				_unblock.clearState();
 | 
						|
				_unblock.show();
 | 
						|
			}
 | 
						|
		} else if (isJoinChannel()) {
 | 
						|
			_unblock.hide();
 | 
						|
			_muteUnmute.hide();
 | 
						|
			if (_joinChannel.isHidden()) {
 | 
						|
				_joinChannel.clearState();
 | 
						|
				_joinChannel.show();
 | 
						|
			}
 | 
						|
		} else if (isMuteUnmute()) {
 | 
						|
			_unblock.hide();
 | 
						|
			_joinChannel.hide();
 | 
						|
			if (_muteUnmute.isHidden()) {
 | 
						|
				_muteUnmute.clearState();
 | 
						|
				_muteUnmute.show();
 | 
						|
			}
 | 
						|
		}
 | 
						|
		_kbShown = false;
 | 
						|
		_fieldAutocomplete->hide();
 | 
						|
		_send.hide();
 | 
						|
		if (_inlineBotCancel) _inlineBotCancel->hide();
 | 
						|
		_botStart.hide();
 | 
						|
		_attachDocument.hide();
 | 
						|
		_attachPhoto.hide();
 | 
						|
		_silent.hide();
 | 
						|
		_kbScroll.hide();
 | 
						|
		_fieldBarCancel.hide();
 | 
						|
		_attachDocument.hide();
 | 
						|
		_attachPhoto.hide();
 | 
						|
		_attachEmoji.hide();
 | 
						|
		_kbShow.hide();
 | 
						|
		_kbHide.hide();
 | 
						|
		_cmdStart.hide();
 | 
						|
		_attachType->hide();
 | 
						|
		_emojiPan->hide();
 | 
						|
		if (!_field.isHidden()) {
 | 
						|
			_field.hide();
 | 
						|
			resizeEvent(0);
 | 
						|
			update();
 | 
						|
		}
 | 
						|
	} else if (_canSendMessages) {
 | 
						|
		onCheckFieldAutocomplete();
 | 
						|
		if (isBotStart()) {
 | 
						|
			_unblock.hide();
 | 
						|
			_joinChannel.hide();
 | 
						|
			_muteUnmute.hide();
 | 
						|
			if (_botStart.isHidden()) {
 | 
						|
				_botStart.clearState();
 | 
						|
				_botStart.show();
 | 
						|
			}
 | 
						|
			_kbShown = false;
 | 
						|
			_send.hide();
 | 
						|
			if (_inlineBotCancel) _inlineBotCancel->hide();
 | 
						|
			_field.hide();
 | 
						|
			_attachEmoji.hide();
 | 
						|
			_kbShow.hide();
 | 
						|
			_kbHide.hide();
 | 
						|
			_cmdStart.hide();
 | 
						|
			_attachDocument.hide();
 | 
						|
			_attachPhoto.hide();
 | 
						|
			_silent.hide();
 | 
						|
			_kbScroll.hide();
 | 
						|
			_fieldBarCancel.hide();
 | 
						|
		} else {
 | 
						|
			_unblock.hide();
 | 
						|
			_botStart.hide();
 | 
						|
			_joinChannel.hide();
 | 
						|
			_muteUnmute.hide();
 | 
						|
			if (cHasAudioCapture() && !_field.hasSendText() && !readyToForward()) {
 | 
						|
				_send.hide();
 | 
						|
				mouseMoveEvent(0);
 | 
						|
			} else {
 | 
						|
				if (_inlineBotCancel) {
 | 
						|
					_inlineBotCancel->show();
 | 
						|
					_send.hide();
 | 
						|
				} else {
 | 
						|
					_send.show();
 | 
						|
				}
 | 
						|
				_a_record.stop();
 | 
						|
				_inRecord = _inField = false;
 | 
						|
				a_recordOver = anim::fvalue(0, 0);
 | 
						|
			}
 | 
						|
			if (_recording) {
 | 
						|
				_field.hide();
 | 
						|
				_attachEmoji.hide();
 | 
						|
				_kbShow.hide();
 | 
						|
				_kbHide.hide();
 | 
						|
				_cmdStart.hide();
 | 
						|
				_attachDocument.hide();
 | 
						|
				_attachPhoto.hide();
 | 
						|
				_silent.hide();
 | 
						|
				if (_kbShown) {
 | 
						|
					_kbScroll.show();
 | 
						|
				} else {
 | 
						|
					_kbScroll.hide();
 | 
						|
				}
 | 
						|
			} else {
 | 
						|
				_field.show();
 | 
						|
				if (_kbShown) {
 | 
						|
					_kbScroll.show();
 | 
						|
					_attachEmoji.hide();
 | 
						|
					_kbHide.show();
 | 
						|
					_kbShow.hide();
 | 
						|
					_cmdStart.hide();
 | 
						|
				} else if (_kbReplyTo) {
 | 
						|
					_kbScroll.hide();
 | 
						|
					_attachEmoji.show();
 | 
						|
					_kbHide.hide();
 | 
						|
					_kbShow.hide();
 | 
						|
					_cmdStart.hide();
 | 
						|
				} else {
 | 
						|
					_kbScroll.hide();
 | 
						|
					_attachEmoji.show();
 | 
						|
					_kbHide.hide();
 | 
						|
					if (_keyboard.hasMarkup()) {
 | 
						|
						_kbShow.show();
 | 
						|
						_cmdStart.hide();
 | 
						|
					} else {
 | 
						|
						_kbShow.hide();
 | 
						|
						if (_cmdStartShown) {
 | 
						|
							_cmdStart.show();
 | 
						|
						} else {
 | 
						|
							_cmdStart.hide();
 | 
						|
						}
 | 
						|
					}
 | 
						|
				}
 | 
						|
				if (cDefaultAttach() == dbidaPhoto) {
 | 
						|
					_attachDocument.hide();
 | 
						|
					_attachPhoto.show();
 | 
						|
				} else {
 | 
						|
					_attachDocument.show();
 | 
						|
					_attachPhoto.hide();
 | 
						|
				}
 | 
						|
				if (hasSilentToggle()) {
 | 
						|
					_silent.show();
 | 
						|
				} else {
 | 
						|
					_silent.hide();
 | 
						|
				}
 | 
						|
				updateFieldPlaceholder();
 | 
						|
			}
 | 
						|
			if (_editMsgId || _replyToId || readyToForward() || (_previewData && _previewData->pendingTill >= 0) || _kbReplyTo) {
 | 
						|
				if (_fieldBarCancel.isHidden()) {
 | 
						|
					_fieldBarCancel.show();
 | 
						|
					resizeEvent(0);
 | 
						|
					update();
 | 
						|
				}
 | 
						|
			} else {
 | 
						|
				_fieldBarCancel.hide();
 | 
						|
			}
 | 
						|
		}
 | 
						|
	} else {
 | 
						|
		_fieldAutocomplete->hide();
 | 
						|
		_send.hide();
 | 
						|
		if (_inlineBotCancel) _inlineBotCancel->hide();
 | 
						|
		_unblock.hide();
 | 
						|
		_botStart.hide();
 | 
						|
		_joinChannel.hide();
 | 
						|
		_muteUnmute.hide();
 | 
						|
		_attachDocument.hide();
 | 
						|
		_attachPhoto.hide();
 | 
						|
		_silent.hide();
 | 
						|
		_kbScroll.hide();
 | 
						|
		_fieldBarCancel.hide();
 | 
						|
		_attachDocument.hide();
 | 
						|
		_attachPhoto.hide();
 | 
						|
		_attachEmoji.hide();
 | 
						|
		_kbShow.hide();
 | 
						|
		_kbHide.hide();
 | 
						|
		_cmdStart.hide();
 | 
						|
		_attachType->hide();
 | 
						|
		_emojiPan->hide();
 | 
						|
		_kbScroll.hide();
 | 
						|
		if (!_field.isHidden()) {
 | 
						|
			_field.hide();
 | 
						|
			resizeEvent(0);
 | 
						|
			update();
 | 
						|
		}
 | 
						|
	}
 | 
						|
	updateMouseTracking();
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::updateMouseTracking() {
 | 
						|
	bool trackMouse = !_fieldBarCancel.isHidden() || _pinnedBar || (cHasAudioCapture() && _send.isHidden() && !_field.isHidden());
 | 
						|
	setMouseTracking(trackMouse);
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::destroyUnreadBar() {
 | 
						|
	if (_history) _history->destroyUnreadBar();
 | 
						|
	if (_migrated) _migrated->destroyUnreadBar();
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::newUnreadMsg(History *history, HistoryItem *item) {
 | 
						|
	if (_history == history) {
 | 
						|
		if (_scroll.scrollTop() + 1 > _scroll.scrollTopMax()) {
 | 
						|
			destroyUnreadBar();
 | 
						|
		}
 | 
						|
		if (App::wnd()->doWeReadServerHistory()) {
 | 
						|
			historyWasRead(ReadServerHistoryChecks::ForceRequest);
 | 
						|
			return;
 | 
						|
		}
 | 
						|
	}
 | 
						|
	App::wnd()->notifySchedule(history, item);
 | 
						|
	history->setUnreadCount(history->unreadCount() + 1);
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::historyToDown(History *history) {
 | 
						|
	history->forgetScrollState();
 | 
						|
	if (auto migrated = App::historyLoaded(history->peer->migrateFrom())) {
 | 
						|
		migrated->forgetScrollState();
 | 
						|
	}
 | 
						|
	if (history == _history) {
 | 
						|
		_scroll.scrollToY(_scroll.scrollTopMax());
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::historyWasRead(ReadServerHistoryChecks checks) {
 | 
						|
	App::main()->readServerHistory(_history, checks);
 | 
						|
	if (_migrated) {
 | 
						|
		App::main()->readServerHistory(_migrated, ReadServerHistoryChecks::OnlyIfUnread);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::unreadCountChanged(History *history) {
 | 
						|
	if (history == _history || history == _migrated) {
 | 
						|
		updateToEndVisibility();
 | 
						|
		if (_historyToEnd) {
 | 
						|
			_historyToEnd->setUnreadCount(_history->unreadCount() + (_migrated ? _migrated->unreadCount() : 0));
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::historyCleared(History *history) {
 | 
						|
	if (history == _history) {
 | 
						|
		_list->dragActionCancel();
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
bool HistoryWidget::messagesFailed(const RPCError &error, mtpRequestId requestId) {
 | 
						|
	if (MTP::isDefaultHandledError(error)) return false;
 | 
						|
 | 
						|
	if (error.type() == qstr("CHANNEL_PRIVATE") || error.type() == qstr("CHANNEL_PUBLIC_GROUP_NA") || error.type() == qstr("USER_BANNED_IN_CHANNEL")) {
 | 
						|
		PeerData *was = _peer;
 | 
						|
		App::main()->showBackFromStack();
 | 
						|
		Ui::showLayer(new InformBox(lang((was && was->isMegagroup()) ? lng_group_not_accessible : lng_channel_not_accessible)));
 | 
						|
		return true;
 | 
						|
	}
 | 
						|
 | 
						|
	LOG(("RPC Error: %1 %2: %3").arg(error.code()).arg(error.type()).arg(error.description()));
 | 
						|
	if (_preloadRequest == requestId) {
 | 
						|
		_preloadRequest = 0;
 | 
						|
	} else if (_preloadDownRequest == requestId) {
 | 
						|
		_preloadDownRequest = 0;
 | 
						|
	} else if (_firstLoadRequest == requestId) {
 | 
						|
		_firstLoadRequest = 0;
 | 
						|
		App::main()->showBackFromStack();
 | 
						|
	} else if (_delayedShowAtRequest == requestId) {
 | 
						|
		_delayedShowAtRequest = 0;
 | 
						|
	}
 | 
						|
	return true;
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::messagesReceived(PeerData *peer, const MTPmessages_Messages &messages, mtpRequestId requestId) {
 | 
						|
	if (!_history) {
 | 
						|
		_preloadRequest = _preloadDownRequest = _firstLoadRequest = _delayedShowAtRequest = 0;
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	bool toMigrated = (peer == _peer->migrateFrom());
 | 
						|
	if (peer != _peer && !toMigrated) {
 | 
						|
		_preloadRequest = _preloadDownRequest = _firstLoadRequest = _delayedShowAtRequest = 0;
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	int32 count = 0;
 | 
						|
	const QVector<MTPMessage> emptyList, *histList = &emptyList;
 | 
						|
	switch (messages.type()) {
 | 
						|
	case mtpc_messages_messages: {
 | 
						|
		auto &d(messages.c_messages_messages());
 | 
						|
		App::feedUsers(d.vusers);
 | 
						|
		App::feedChats(d.vchats);
 | 
						|
		histList = &d.vmessages.c_vector().v;
 | 
						|
		count = histList->size();
 | 
						|
	} break;
 | 
						|
	case mtpc_messages_messagesSlice: {
 | 
						|
		auto &d(messages.c_messages_messagesSlice());
 | 
						|
		App::feedUsers(d.vusers);
 | 
						|
		App::feedChats(d.vchats);
 | 
						|
		histList = &d.vmessages.c_vector().v;
 | 
						|
		count = d.vcount.v;
 | 
						|
	} break;
 | 
						|
	case mtpc_messages_channelMessages: {
 | 
						|
		auto &d(messages.c_messages_channelMessages());
 | 
						|
		if (peer && peer->isChannel()) {
 | 
						|
			peer->asChannel()->ptsReceived(d.vpts.v);
 | 
						|
		} else {
 | 
						|
			LOG(("API Error: received messages.channelMessages when no channel was passed! (HistoryWidget::messagesReceived)"));
 | 
						|
		}
 | 
						|
		App::feedUsers(d.vusers);
 | 
						|
		App::feedChats(d.vchats);
 | 
						|
		histList = &d.vmessages.c_vector().v;
 | 
						|
		count = d.vcount.v;
 | 
						|
	} break;
 | 
						|
	}
 | 
						|
 | 
						|
	if (_preloadRequest == requestId) {
 | 
						|
		addMessagesToFront(peer, *histList);
 | 
						|
		_preloadRequest = 0;
 | 
						|
		preloadHistoryIfNeeded();
 | 
						|
		if (_reportSpamStatus == dbiprsUnknown) {
 | 
						|
			updateReportSpamStatus();
 | 
						|
			if (_reportSpamStatus != dbiprsUnknown) updateControlsVisibility();
 | 
						|
		}
 | 
						|
	} else if (_preloadDownRequest == requestId) {
 | 
						|
		addMessagesToBack(peer, *histList);
 | 
						|
		_preloadDownRequest = 0;
 | 
						|
		preloadHistoryIfNeeded();
 | 
						|
		if (_history->loadedAtBottom() && App::wnd()) App::wnd()->checkHistoryActivation();
 | 
						|
	} else if (_firstLoadRequest == requestId) {
 | 
						|
		if (toMigrated) {
 | 
						|
			_history->clear(true);
 | 
						|
		} else if (_migrated) {
 | 
						|
			_migrated->clear(true);
 | 
						|
		}
 | 
						|
		addMessagesToFront(peer, *histList);
 | 
						|
		_firstLoadRequest = 0;
 | 
						|
		if (_history->loadedAtTop()) {
 | 
						|
			if (_history->unreadCount() > count) {
 | 
						|
				_history->setUnreadCount(count);
 | 
						|
			}
 | 
						|
			if (_history->isEmpty() && count > 0) {
 | 
						|
				firstLoadMessages();
 | 
						|
				return;
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		historyLoaded();
 | 
						|
	} else if (_delayedShowAtRequest == requestId) {
 | 
						|
		if (toMigrated) {
 | 
						|
			_history->clear(true);
 | 
						|
		} else if (_migrated) {
 | 
						|
			_migrated->clear(true);
 | 
						|
		}
 | 
						|
 | 
						|
		_delayedShowAtRequest = 0;
 | 
						|
		_history->getReadyFor(_delayedShowAtMsgId);
 | 
						|
		if (_history->isEmpty()) {
 | 
						|
			if (_preloadRequest) MTP::cancel(_preloadRequest);
 | 
						|
			if (_preloadDownRequest) MTP::cancel(_preloadDownRequest);
 | 
						|
			if (_firstLoadRequest) MTP::cancel(_firstLoadRequest);
 | 
						|
			_preloadRequest = _preloadDownRequest = 0;
 | 
						|
			_firstLoadRequest = -1; // hack - don't updateListSize yet
 | 
						|
			addMessagesToFront(peer, *histList);
 | 
						|
			_firstLoadRequest = 0;
 | 
						|
			if (_history->loadedAtTop()) {
 | 
						|
				if (_history->unreadCount() > count) {
 | 
						|
					_history->setUnreadCount(count);
 | 
						|
				}
 | 
						|
				if (_history->isEmpty() && count > 0) {
 | 
						|
					firstLoadMessages();
 | 
						|
					return;
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
		if (_replyReturn) {
 | 
						|
			if (_replyReturn->history() == _history && _replyReturn->id == _delayedShowAtMsgId) {
 | 
						|
				calcNextReplyReturn();
 | 
						|
			} else if (_replyReturn->history() == _migrated && -_replyReturn->id == _delayedShowAtMsgId) {
 | 
						|
				calcNextReplyReturn();
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		setMsgId(_delayedShowAtMsgId);
 | 
						|
 | 
						|
		_histInited = false;
 | 
						|
 | 
						|
		historyLoaded();
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::historyLoaded() {
 | 
						|
	countHistoryShowFrom();
 | 
						|
	destroyUnreadBar();
 | 
						|
	doneShow();
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::windowShown() {
 | 
						|
	resizeEvent(0);
 | 
						|
}
 | 
						|
 | 
						|
bool HistoryWidget::doWeReadServerHistory() const {
 | 
						|
	if (!_history || !_list) return true;
 | 
						|
	if (_firstLoadRequest || _a_show.animating()) return false;
 | 
						|
	if (_history->loadedAtBottom()) {
 | 
						|
		int scrollTop = _scroll.scrollTop();
 | 
						|
		if (scrollTop + 1 > _scroll.scrollTopMax()) return true;
 | 
						|
 | 
						|
		auto showFrom = (_migrated && _migrated->showFrom) ? _migrated->showFrom : (_history ? _history->showFrom : nullptr);
 | 
						|
		if (showFrom && !showFrom->detached()) {
 | 
						|
			int scrollBottom = scrollTop + _scroll.height();
 | 
						|
			if (scrollBottom > _list->itemTop(showFrom)) return true;
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if (historyHasNotFreezedUnreadBar(_history)) {
 | 
						|
		return true;
 | 
						|
	}
 | 
						|
	if (historyHasNotFreezedUnreadBar(_migrated)) {
 | 
						|
		return true;
 | 
						|
	}
 | 
						|
	return false;
 | 
						|
}
 | 
						|
 | 
						|
bool HistoryWidget::historyHasNotFreezedUnreadBar(History *history) const {
 | 
						|
	if (history && history->showFrom && !history->showFrom->detached() && history->unreadBar) {
 | 
						|
		if (auto unreadBar = history->unreadBar->Get<HistoryMessageUnreadBar>()) {
 | 
						|
			return !unreadBar->_freezed;
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return false;
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::firstLoadMessages() {
 | 
						|
	if (!_history || _firstLoadRequest) return;
 | 
						|
 | 
						|
	PeerData *from = _peer;
 | 
						|
	int32 offset_id = 0, offset = 0, loadCount = MessagesPerPage;
 | 
						|
	if (_showAtMsgId == ShowAtUnreadMsgId) {
 | 
						|
		if (_migrated && _migrated->unreadCount()) {
 | 
						|
			_history->getReadyFor(_showAtMsgId);
 | 
						|
			from = _migrated->peer;
 | 
						|
			offset = -loadCount / 2;
 | 
						|
			offset_id = _migrated->inboxReadBefore;
 | 
						|
		} else if (_history->unreadCount()) {
 | 
						|
			_history->getReadyFor(_showAtMsgId);
 | 
						|
			offset = -loadCount / 2;
 | 
						|
			offset_id = _history->inboxReadBefore;
 | 
						|
		} else {
 | 
						|
			_history->getReadyFor(ShowAtTheEndMsgId);
 | 
						|
		}
 | 
						|
	} else if (_showAtMsgId == ShowAtTheEndMsgId) {
 | 
						|
		_history->getReadyFor(_showAtMsgId);
 | 
						|
		loadCount = MessagesFirstLoad;
 | 
						|
	} else if (_showAtMsgId > 0) {
 | 
						|
		_history->getReadyFor(_showAtMsgId);
 | 
						|
		offset = -loadCount / 2;
 | 
						|
		offset_id = _showAtMsgId;
 | 
						|
	} else if (_showAtMsgId < 0 && _history->isChannel()) {
 | 
						|
		if (_showAtMsgId < 0 && -_showAtMsgId < ServerMaxMsgId && _migrated) {
 | 
						|
			_history->getReadyFor(_showAtMsgId);
 | 
						|
			from = _migrated->peer;
 | 
						|
			offset = -loadCount / 2;
 | 
						|
			offset_id = -_showAtMsgId;
 | 
						|
		} else if (_showAtMsgId == SwitchAtTopMsgId) {
 | 
						|
			_history->getReadyFor(_showAtMsgId);
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	_firstLoadRequest = MTP::send(MTPmessages_GetHistory(from->input, MTP_int(offset_id), MTP_int(0), MTP_int(offset), MTP_int(loadCount), MTP_int(0), MTP_int(0)), rpcDone(&HistoryWidget::messagesReceived, from), rpcFail(&HistoryWidget::messagesFailed));
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::loadMessages() {
 | 
						|
	if (!_history || _preloadRequest) return;
 | 
						|
 | 
						|
	if (_history->isEmpty() && _migrated && _migrated->isEmpty()) {
 | 
						|
		return firstLoadMessages();
 | 
						|
	}
 | 
						|
 | 
						|
	bool loadMigrated = _migrated && (_history->isEmpty() || _history->loadedAtTop() || (!_migrated->isEmpty() && !_migrated->loadedAtBottom()));
 | 
						|
	History *from = loadMigrated ? _migrated : _history;
 | 
						|
	if (from->loadedAtTop()) {
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	MsgId offset_id = from->minMsgId();
 | 
						|
	int32 offset = 0, loadCount = offset_id ? MessagesPerPage : MessagesFirstLoad;
 | 
						|
 | 
						|
	_preloadRequest = MTP::send(MTPmessages_GetHistory(from->peer->input, MTP_int(offset_id), MTP_int(0), MTP_int(offset), MTP_int(loadCount), MTP_int(0), MTP_int(0)), rpcDone(&HistoryWidget::messagesReceived, from->peer), rpcFail(&HistoryWidget::messagesFailed));
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::loadMessagesDown() {
 | 
						|
	if (!_history || _preloadDownRequest) return;
 | 
						|
 | 
						|
	if (_history->isEmpty() && _migrated && _migrated->isEmpty()) {
 | 
						|
		return firstLoadMessages();
 | 
						|
	}
 | 
						|
 | 
						|
	bool loadMigrated = _migrated && !(_migrated->isEmpty() || _migrated->loadedAtBottom() || (!_history->isEmpty() && !_history->loadedAtTop()));
 | 
						|
	History *from = loadMigrated ? _migrated : _history;
 | 
						|
	if (from->loadedAtBottom()) {
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	int32 loadCount = MessagesPerPage, offset = -loadCount;
 | 
						|
 | 
						|
	MsgId offset_id = from->maxMsgId();
 | 
						|
	if (!offset_id) {
 | 
						|
		if (loadMigrated || !_migrated) return;
 | 
						|
		++offset_id;
 | 
						|
		++offset;
 | 
						|
	}
 | 
						|
 | 
						|
	_preloadDownRequest = MTP::send(MTPmessages_GetHistory(from->peer->input, MTP_int(offset_id + 1), MTP_int(0), MTP_int(offset), MTP_int(loadCount), MTP_int(0), MTP_int(0)), rpcDone(&HistoryWidget::messagesReceived, from->peer), rpcFail(&HistoryWidget::messagesFailed));
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::delayedShowAt(MsgId showAtMsgId) {
 | 
						|
	if (!_history || (_delayedShowAtRequest && _delayedShowAtMsgId == showAtMsgId)) return;
 | 
						|
 | 
						|
	clearDelayedShowAt();
 | 
						|
	_delayedShowAtMsgId = showAtMsgId;
 | 
						|
 | 
						|
	PeerData *from = _peer;
 | 
						|
	int32 offset_id = 0, offset = 0, loadCount = MessagesPerPage;
 | 
						|
	if (_delayedShowAtMsgId == ShowAtUnreadMsgId) {
 | 
						|
		if (_migrated && _migrated->unreadCount()) {
 | 
						|
			from = _migrated->peer;
 | 
						|
			offset = -loadCount / 2;
 | 
						|
			offset_id = _migrated->inboxReadBefore;
 | 
						|
		} else if (_history->unreadCount()) {
 | 
						|
			offset = -loadCount / 2;
 | 
						|
			offset_id = _history->inboxReadBefore;
 | 
						|
		} else {
 | 
						|
			loadCount = MessagesFirstLoad;
 | 
						|
		}
 | 
						|
	} else if (_delayedShowAtMsgId == ShowAtTheEndMsgId) {
 | 
						|
		loadCount = MessagesFirstLoad;
 | 
						|
	} else if (_delayedShowAtMsgId > 0) {
 | 
						|
		offset = -loadCount / 2;
 | 
						|
		offset_id = _delayedShowAtMsgId;
 | 
						|
	} else if (_delayedShowAtMsgId < 0 && _history->isChannel()) {
 | 
						|
		if (_delayedShowAtMsgId < 0 && -_delayedShowAtMsgId < ServerMaxMsgId && _migrated) {
 | 
						|
			from = _migrated->peer;
 | 
						|
			offset = -loadCount / 2;
 | 
						|
			offset_id = -_delayedShowAtMsgId;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	_delayedShowAtRequest = MTP::send(MTPmessages_GetHistory(from->input, MTP_int(offset_id), MTP_int(0), MTP_int(offset), MTP_int(loadCount), MTP_int(0), MTP_int(0)), rpcDone(&HistoryWidget::messagesReceived, from), rpcFail(&HistoryWidget::messagesFailed));
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::onScroll() {
 | 
						|
	App::checkImageCacheSize();
 | 
						|
	preloadHistoryIfNeeded();
 | 
						|
	visibleAreaUpdated();
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::visibleAreaUpdated() {
 | 
						|
	if (_list && !_scroll.isHidden()) {
 | 
						|
		int scrollTop = _scroll.scrollTop();
 | 
						|
		int scrollBottom = scrollTop + _scroll.height();
 | 
						|
		_list->visibleAreaUpdated(scrollTop, scrollBottom);
 | 
						|
		if (_history->loadedAtBottom() && (_history->unreadCount() > 0 || (_migrated && _migrated->unreadCount() > 0))) {
 | 
						|
			auto showFrom = (_migrated && _migrated->showFrom) ? _migrated->showFrom : (_history ? _history->showFrom : nullptr);
 | 
						|
			if (showFrom && !showFrom->detached() && scrollBottom > _list->itemTop(showFrom) && App::wnd()->doWeReadServerHistory()) {
 | 
						|
				historyWasRead(ReadServerHistoryChecks::OnlyIfUnread);
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::preloadHistoryIfNeeded() {
 | 
						|
	if (_firstLoadRequest || _scroll.isHidden() || !_peer) return;
 | 
						|
 | 
						|
	updateToEndVisibility();
 | 
						|
 | 
						|
	int st = _scroll.scrollTop(), stm = _scroll.scrollTopMax(), sh = _scroll.height();
 | 
						|
	if (st + PreloadHeightsCount * sh > stm) {
 | 
						|
		loadMessagesDown();
 | 
						|
	}
 | 
						|
 | 
						|
	if (st < PreloadHeightsCount * sh) {
 | 
						|
		loadMessages();
 | 
						|
	}
 | 
						|
 | 
						|
	while (_replyReturn) {
 | 
						|
		bool below = (_replyReturn->detached() && _replyReturn->history() == _history && !_history->isEmpty() && _replyReturn->id < _history->blocks.back()->items.back()->id);
 | 
						|
		if (!below) below = (_replyReturn->detached() && _replyReturn->history() == _migrated && !_history->isEmpty());
 | 
						|
		if (!below) below = (_replyReturn->detached() && _migrated && _replyReturn->history() == _migrated && !_migrated->isEmpty() && _replyReturn->id < _migrated->blocks.back()->items.back()->id);
 | 
						|
		if (!below && !_replyReturn->detached()) below = (st >= stm) || (_list->itemTop(_replyReturn) < st + sh / 2);
 | 
						|
		if (below) {
 | 
						|
			calcNextReplyReturn();
 | 
						|
		} else {
 | 
						|
			break;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if (st != _lastScroll) {
 | 
						|
		_lastScrolled = getms();
 | 
						|
		_lastScroll = st;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::onInlineBotCancel() {
 | 
						|
	auto &textWithTags = _field.getTextWithTags();
 | 
						|
	if (textWithTags.text.size() > _inlineBotUsername.size() + 2) {
 | 
						|
		setFieldText({ '@' + _inlineBotUsername + ' ', TextWithTags::Tags() }, TextUpdateEvent::SaveDraft, FlatTextarea::AddToUndoHistory);
 | 
						|
	} else {
 | 
						|
		clearFieldText(TextUpdateEvent::SaveDraft, FlatTextarea::AddToUndoHistory);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::onWindowVisibleChanged() {
 | 
						|
	QTimer::singleShot(0, this, SLOT(preloadHistoryIfNeeded()));
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::onHistoryToEnd() {
 | 
						|
	if (_replyReturn && _replyReturn->history() == _history) {
 | 
						|
		showHistory(_peer->id, _replyReturn->id);
 | 
						|
	} else if (_replyReturn && _replyReturn->history() == _migrated) {
 | 
						|
		showHistory(_peer->id, -_replyReturn->id);
 | 
						|
	} else if (_peer) {
 | 
						|
		showHistory(_peer->id, ShowAtUnreadMsgId);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::saveEditMsg() {
 | 
						|
	if (_saveEditMsgRequestId) return;
 | 
						|
 | 
						|
	WebPageId webPageId = _previewCancelled ? CancelledWebPageId : ((_previewData && _previewData->pendingTill >= 0) ? _previewData->id : 0);
 | 
						|
 | 
						|
	auto &textWithTags = _field.getTextWithTags();
 | 
						|
	auto prepareFlags = itemTextOptions(_history, App::self()).flags;
 | 
						|
	EntitiesInText sendingEntities, leftEntities = entitiesFromTextTags(textWithTags.tags);
 | 
						|
	QString sendingText, leftText = prepareTextWithEntities(textWithTags.text, prepareFlags, &leftEntities);
 | 
						|
 | 
						|
	if (!textSplit(sendingText, sendingEntities, leftText, leftEntities, MaxMessageSize)) {
 | 
						|
		_field.selectAll();
 | 
						|
		_field.setFocus();
 | 
						|
		return;
 | 
						|
	} else if (!leftText.isEmpty()) {
 | 
						|
		Ui::showLayer(new InformBox(lang(lng_edit_too_long)));
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	MTPmessages_EditMessage::Flags sendFlags = MTPmessages_EditMessage::Flag::f_message;
 | 
						|
	if (webPageId == CancelledWebPageId) {
 | 
						|
		sendFlags |= MTPmessages_EditMessage::Flag::f_no_webpage;
 | 
						|
	}
 | 
						|
	MTPVector<MTPMessageEntity> localEntities = linksToMTP(sendingEntities), sentEntities = linksToMTP(sendingEntities, true);
 | 
						|
	if (!sentEntities.c_vector().v.isEmpty()) {
 | 
						|
		sendFlags |= MTPmessages_EditMessage::Flag::f_entities;
 | 
						|
	}
 | 
						|
	_saveEditMsgRequestId = MTP::send(MTPmessages_EditMessage(MTP_flags(sendFlags), _history->peer->input, MTP_int(_editMsgId), MTP_string(sendingText), MTPnullMarkup, sentEntities), rpcDone(&HistoryWidget::saveEditMsgDone, _history), rpcFail(&HistoryWidget::saveEditMsgFail, _history));
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::saveEditMsgDone(History *history, const MTPUpdates &updates, mtpRequestId req) {
 | 
						|
	if (App::main()) {
 | 
						|
		App::main()->sentUpdatesReceived(updates);
 | 
						|
	}
 | 
						|
	if (req == _saveEditMsgRequestId) {
 | 
						|
		_saveEditMsgRequestId = 0;
 | 
						|
		cancelEdit();
 | 
						|
	}
 | 
						|
	if (auto editDraft = history->editDraft()) {
 | 
						|
		if (editDraft->saveRequestId == req) {
 | 
						|
			history->clearEditDraft();
 | 
						|
			if (App::main()) App::main()->writeDrafts(history);
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
bool HistoryWidget::saveEditMsgFail(History *history, const RPCError &error, mtpRequestId req) {
 | 
						|
	if (MTP::isDefaultHandledError(error)) return false;
 | 
						|
	if (req == _saveEditMsgRequestId) {
 | 
						|
		_saveEditMsgRequestId = 0;
 | 
						|
	}
 | 
						|
	if (auto editDraft = history->editDraft()) {
 | 
						|
		if (editDraft->saveRequestId == req) {
 | 
						|
			editDraft->saveRequestId = 0;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	QString err = error.type();
 | 
						|
	if (err == qstr("MESSAGE_ID_INVALID") || err == qstr("CHAT_ADMIN_REQUIRED") || err == qstr("MESSAGE_EDIT_TIME_EXPIRED")) {
 | 
						|
		Ui::showLayer(new InformBox(lang(lng_edit_error)));
 | 
						|
	} else if (err == qstr("MESSAGE_NOT_MODIFIED")) {
 | 
						|
		cancelEdit();
 | 
						|
	} else if (err == qstr("MESSAGE_EMPTY")) {
 | 
						|
		_field.selectAll();
 | 
						|
		_field.setFocus();
 | 
						|
	} else {
 | 
						|
		Ui::showLayer(new InformBox(lang(lng_edit_error)));
 | 
						|
	}
 | 
						|
	update();
 | 
						|
	return true;
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::onSend(bool ctrlShiftEnter, MsgId replyTo) {
 | 
						|
	if (!_history) return;
 | 
						|
 | 
						|
	if (_editMsgId) {
 | 
						|
		saveEditMsg();
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	bool lastKeyboardUsed = lastForceReplyReplied(FullMsgId(_channel, replyTo));
 | 
						|
 | 
						|
	WebPageId webPageId = _previewCancelled ? CancelledWebPageId : ((_previewData && _previewData->pendingTill >= 0) ? _previewData->id : 0);
 | 
						|
 | 
						|
	MainWidget::MessageToSend message;
 | 
						|
	message.history = _history;
 | 
						|
	message.textWithTags = _field.getTextWithTags();
 | 
						|
	message.replyTo = replyTo;
 | 
						|
	message.silent = _silent.checked();
 | 
						|
	message.webPageId = webPageId;
 | 
						|
	App::main()->sendMessage(message);
 | 
						|
 | 
						|
	clearFieldText();
 | 
						|
	_saveDraftText = true;
 | 
						|
	_saveDraftStart = getms();
 | 
						|
	onDraftSave();
 | 
						|
 | 
						|
	if (!_fieldAutocomplete->isHidden()) _fieldAutocomplete->hideStart();
 | 
						|
	if (!_attachType->isHidden()) _attachType->hideStart();
 | 
						|
	if (!_emojiPan->isHidden()) _emojiPan->hideStart();
 | 
						|
 | 
						|
	if (replyTo < 0) cancelReply(lastKeyboardUsed);
 | 
						|
	if (_previewData && _previewData->pendingTill) previewCancel();
 | 
						|
	_field.setFocus();
 | 
						|
 | 
						|
	if (!_keyboard.hasMarkup() && _keyboard.forceReply() && !_kbReplyTo) onKbToggle();
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::onUnblock() {
 | 
						|
	if (_unblockRequest) return;
 | 
						|
	if (!_peer || !_peer->isUser() || !_peer->asUser()->isBlocked()) {
 | 
						|
		updateControlsVisibility();
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	_unblockRequest = MTP::send(MTPcontacts_Unblock(_peer->asUser()->inputUser), rpcDone(&HistoryWidget::unblockDone, _peer), rpcFail(&HistoryWidget::unblockFail));
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::unblockDone(PeerData *peer, const MTPBool &result, mtpRequestId req) {
 | 
						|
	if (!peer->isUser()) return;
 | 
						|
	if (_unblockRequest == req) _unblockRequest = 0;
 | 
						|
	peer->asUser()->setBlockStatus(UserData::BlockStatus::NotBlocked);
 | 
						|
	emit App::main()->peerUpdated(peer);
 | 
						|
}
 | 
						|
 | 
						|
bool HistoryWidget::unblockFail(const RPCError &error, mtpRequestId req) {
 | 
						|
	if (MTP::isDefaultHandledError(error)) return false;
 | 
						|
 | 
						|
	if (_unblockRequest == req) _unblockRequest = 0;
 | 
						|
	return false;
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::blockDone(PeerData *peer, const MTPBool &result) {
 | 
						|
	if (!peer->isUser()) return;
 | 
						|
 | 
						|
	peer->asUser()->setBlockStatus(UserData::BlockStatus::Blocked);
 | 
						|
	emit App::main()->peerUpdated(peer);
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::onBotStart() {
 | 
						|
	if (!_peer || !_peer->isUser() || !_peer->asUser()->botInfo || !_canSendMessages) {
 | 
						|
		updateControlsVisibility();
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	QString token = _peer->asUser()->botInfo->startToken;
 | 
						|
	if (token.isEmpty()) {
 | 
						|
		sendBotCommand(_peer, _peer->asUser(), qsl("/start"), 0);
 | 
						|
	} else {
 | 
						|
		uint64 randomId = rand_value<uint64>();
 | 
						|
		MTP::send(MTPmessages_StartBot(_peer->asUser()->inputUser, MTP_inputPeerEmpty(), MTP_long(randomId), MTP_string(token)), App::main()->rpcDone(&MainWidget::sentUpdatesReceived), App::main()->rpcFail(&MainWidget::addParticipantFail, _peer->asUser()));
 | 
						|
 | 
						|
		_peer->asUser()->botInfo->startToken = QString();
 | 
						|
		if (_keyboard.hasMarkup()) {
 | 
						|
			if (_keyboard.singleUse() && _keyboard.forMsgId() == FullMsgId(_channel, _history->lastKeyboardId) && _history->lastKeyboardUsed) {
 | 
						|
				_history->lastKeyboardHiddenId = _history->lastKeyboardId;
 | 
						|
			}
 | 
						|
			if (!kbWasHidden()) _kbShown = _keyboard.hasMarkup();
 | 
						|
		}
 | 
						|
	}
 | 
						|
	updateControlsVisibility();
 | 
						|
	updateControlsGeometry();
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::onJoinChannel() {
 | 
						|
	if (_unblockRequest) return;
 | 
						|
	if (!_peer || !_peer->isChannel() || !isJoinChannel()) {
 | 
						|
		updateControlsVisibility();
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	_unblockRequest = MTP::send(MTPchannels_JoinChannel(_peer->asChannel()->inputChannel), rpcDone(&HistoryWidget::joinDone), rpcFail(&HistoryWidget::joinFail));
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::joinDone(const MTPUpdates &result, mtpRequestId req) {
 | 
						|
	if (_unblockRequest == req) _unblockRequest = 0;
 | 
						|
	if (App::main()) App::main()->sentUpdatesReceived(result);
 | 
						|
}
 | 
						|
 | 
						|
bool HistoryWidget::joinFail(const RPCError &error, mtpRequestId req) {
 | 
						|
	if (MTP::isDefaultHandledError(error)) return false;
 | 
						|
 | 
						|
	if (_unblockRequest == req) _unblockRequest = 0;
 | 
						|
	if (error.type() == qstr("CHANNEL_PRIVATE") || error.type() == qstr("CHANNEL_PUBLIC_GROUP_NA") || error.type() == qstr("USER_BANNED_IN_CHANNEL")) {
 | 
						|
		Ui::showLayer(new InformBox(lang((_peer && _peer->isMegagroup()) ? lng_group_not_accessible : lng_channel_not_accessible)));
 | 
						|
		return true;
 | 
						|
	} else if (error.type() == qstr("CHANNELS_TOO_MUCH")) {
 | 
						|
		Ui::showLayer(new InformBox(lang(lng_join_channel_error)));
 | 
						|
	}
 | 
						|
 | 
						|
	return false;
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::onMuteUnmute() {
 | 
						|
	App::main()->updateNotifySetting(_peer, _history->mute() ? NotifySettingSetNotify : NotifySettingSetMuted);
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::onBroadcastSilentChange() {
 | 
						|
	updateFieldPlaceholder();
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::onShareContact(const PeerId &peer, UserData *contact) {
 | 
						|
	auto phone = contact->phone();
 | 
						|
	if (phone.isEmpty()) phone = App::phoneFromSharedContact(peerToUser(contact->id));
 | 
						|
	if (!contact || phone.isEmpty()) return;
 | 
						|
 | 
						|
	Ui::showPeerHistory(peer, ShowAtTheEndMsgId);
 | 
						|
	if (!_history) return;
 | 
						|
 | 
						|
	shareContact(peer, phone, contact->firstName, contact->lastName, replyToId(), peerToUser(contact->id));
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::shareContact(const PeerId &peer, const QString &phone, const QString &fname, const QString &lname, MsgId replyTo, int32 userId) {
 | 
						|
	auto history = App::history(peer);
 | 
						|
 | 
						|
	uint64 randomId = rand_value<uint64>();
 | 
						|
	FullMsgId newId(peerToChannel(peer), clientMsgId());
 | 
						|
 | 
						|
	App::main()->readServerHistory(history);
 | 
						|
	fastShowAtEnd(history);
 | 
						|
 | 
						|
	PeerData *p = App::peer(peer);
 | 
						|
	MTPDmessage::Flags flags = newMessageFlags(p) | MTPDmessage::Flag::f_media; // unread, out
 | 
						|
 | 
						|
	bool lastKeyboardUsed = lastForceReplyReplied(FullMsgId(peerToChannel(peer), replyTo));
 | 
						|
 | 
						|
	MTPmessages_SendMedia::Flags sendFlags = 0;
 | 
						|
	if (replyTo) {
 | 
						|
		flags |= MTPDmessage::Flag::f_reply_to_msg_id;
 | 
						|
		sendFlags |= MTPmessages_SendMedia::Flag::f_reply_to_msg_id;
 | 
						|
	}
 | 
						|
 | 
						|
	bool channelPost = p->isChannel() && !p->isMegagroup();
 | 
						|
	bool showFromName = !channelPost || p->asChannel()->addsSignature();
 | 
						|
	bool silentPost = channelPost && _silent.checked();
 | 
						|
	if (channelPost) {
 | 
						|
		flags |= MTPDmessage::Flag::f_views;
 | 
						|
		flags |= MTPDmessage::Flag::f_post;
 | 
						|
	}
 | 
						|
	if (showFromName) {
 | 
						|
		flags |= MTPDmessage::Flag::f_from_id;
 | 
						|
	}
 | 
						|
	if (silentPost) {
 | 
						|
		sendFlags |= MTPmessages_SendMedia::Flag::f_silent;
 | 
						|
	}
 | 
						|
	history->addNewMessage(MTP_message(MTP_flags(flags), MTP_int(newId.msg), MTP_int(showFromName ? MTP::authedId() : 0), peerToMTP(peer), MTPnullFwdHeader, MTPint(), MTP_int(replyToId()), MTP_int(unixtime()), MTP_string(""), MTP_messageMediaContact(MTP_string(phone), MTP_string(fname), MTP_string(lname), MTP_int(userId)), MTPnullMarkup, MTPnullEntities, MTP_int(1), MTPint()), NewMessageUnread);
 | 
						|
	history->sendRequestId = MTP::send(MTPmessages_SendMedia(MTP_flags(sendFlags), p->input, MTP_int(replyTo), MTP_inputMediaContact(MTP_string(phone), MTP_string(fname), MTP_string(lname)), MTP_long(randomId), MTPnullMarkup), App::main()->rpcDone(&MainWidget::sentUpdatesReceived), App::main()->rpcFail(&MainWidget::sendMessageFail), 0, 0, history->sendRequestId);
 | 
						|
 | 
						|
	App::historyRegRandom(randomId, newId);
 | 
						|
 | 
						|
	App::main()->finishForwarding(history, _silent.checked());
 | 
						|
	cancelReplyAfterMediaSend(lastKeyboardUsed);
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::onSendPaths(const PeerId &peer) {
 | 
						|
	Ui::showPeerHistory(peer, ShowAtTheEndMsgId);
 | 
						|
	if (!_history) return;
 | 
						|
 | 
						|
	if (cSendPaths().size() == 1) {
 | 
						|
		uploadFile(cSendPaths().at(0), PrepareAuto);
 | 
						|
	} else {
 | 
						|
		uploadFiles(cSendPaths(), PrepareDocument);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
History *HistoryWidget::history() const {
 | 
						|
	return _history;
 | 
						|
}
 | 
						|
 | 
						|
PeerData *HistoryWidget::peer() const {
 | 
						|
	return _peer;
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::setMsgId(MsgId showAtMsgId) { // sometimes _showAtMsgId is set directly
 | 
						|
	if (_showAtMsgId != showAtMsgId) {
 | 
						|
		MsgId wasMsgId = _showAtMsgId;
 | 
						|
		_showAtMsgId = showAtMsgId;
 | 
						|
		App::main()->dlgUpdated(_history, wasMsgId);
 | 
						|
		emit historyShown(_history, _showAtMsgId);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
MsgId HistoryWidget::msgId() const {
 | 
						|
	return _showAtMsgId;
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::showAnimated(Window::SlideDirection direction, const Window::SectionSlideParams ¶ms) {
 | 
						|
	if (App::app()) App::app()->mtpPause();
 | 
						|
 | 
						|
	_cacheUnder = params.oldContentCache;
 | 
						|
	show();
 | 
						|
	_topShadow->setVisible(params.withTopBarShadow ? false : true);
 | 
						|
	_historyToEnd->finishAnimation();
 | 
						|
	_cacheOver = App::main()->grabForShowAnimation(params);
 | 
						|
	App::main()->topBar()->startAnim();
 | 
						|
	_topShadow->setVisible(params.withTopBarShadow ? true : false);
 | 
						|
 | 
						|
	_scroll.hide();
 | 
						|
	_kbScroll.hide();
 | 
						|
	_reportSpamPanel.hide();
 | 
						|
	_historyToEnd->hide();
 | 
						|
	_attachDocument.hide();
 | 
						|
	_attachPhoto.hide();
 | 
						|
	_attachEmoji.hide();
 | 
						|
	_fieldAutocomplete->hide();
 | 
						|
	_silent.hide();
 | 
						|
	_kbShow.hide();
 | 
						|
	_kbHide.hide();
 | 
						|
	_cmdStart.hide();
 | 
						|
	_field.hide();
 | 
						|
	_fieldBarCancel.hide();
 | 
						|
	_send.hide();
 | 
						|
	if (_inlineBotCancel) _inlineBotCancel->hide();
 | 
						|
	_unblock.hide();
 | 
						|
	_botStart.hide();
 | 
						|
	_joinChannel.hide();
 | 
						|
	_muteUnmute.hide();
 | 
						|
	if (_pinnedBar) {
 | 
						|
		_pinnedBar->shadow->hide();
 | 
						|
		_pinnedBar->cancel->hide();
 | 
						|
	}
 | 
						|
 | 
						|
	int delta = st::slideShift;
 | 
						|
	if (direction == Window::SlideDirection::FromLeft) {
 | 
						|
		a_progress = anim::fvalue(1, 0);
 | 
						|
		std::swap(_cacheUnder, _cacheOver);
 | 
						|
		a_coordUnder = anim::ivalue(-delta, 0);
 | 
						|
		a_coordOver = anim::ivalue(0, width());
 | 
						|
	} else {
 | 
						|
		a_progress = anim::fvalue(0, 1);
 | 
						|
		a_coordUnder = anim::ivalue(0, -delta);
 | 
						|
		a_coordOver = anim::ivalue(width(), 0);
 | 
						|
	}
 | 
						|
	_a_show.start();
 | 
						|
 | 
						|
	App::main()->topBar()->update();
 | 
						|
 | 
						|
	activate();
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::step_show(float64 ms, bool timer) {
 | 
						|
	float64 dt = ms / st::slideDuration;
 | 
						|
	if (dt >= 1) {
 | 
						|
		_a_show.stop();
 | 
						|
		_topShadow->setVisible(_peer ? true : false);
 | 
						|
		_historyToEnd->finishAnimation();
 | 
						|
 | 
						|
		a_coordUnder.finish();
 | 
						|
		a_coordOver.finish();
 | 
						|
		a_progress.finish();
 | 
						|
		_cacheUnder = _cacheOver = QPixmap();
 | 
						|
		App::main()->topBar()->stopAnim();
 | 
						|
		doneShow();
 | 
						|
 | 
						|
		if (App::app()) App::app()->mtpUnpause();
 | 
						|
	} else {
 | 
						|
		a_coordUnder.update(dt, st::slideFunction);
 | 
						|
		a_coordOver.update(dt, st::slideFunction);
 | 
						|
		a_progress.update(dt, st::slideFunction);
 | 
						|
	}
 | 
						|
	if (timer) {
 | 
						|
		update();
 | 
						|
		App::main()->topBar()->update();
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::doneShow() {
 | 
						|
	updateReportSpamStatus();
 | 
						|
	updateBotKeyboard();
 | 
						|
	updateControlsVisibility();
 | 
						|
	if (!_histInited) {
 | 
						|
		updateListSize(true);
 | 
						|
	} else if (hasPendingResizedItems()) {
 | 
						|
		updateListSize();
 | 
						|
	}
 | 
						|
	preloadHistoryIfNeeded();
 | 
						|
	if (App::wnd()) {
 | 
						|
		App::wnd()->checkHistoryActivation();
 | 
						|
		App::wnd()->setInnerFocus();
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::animStop() {
 | 
						|
	if (!_a_show.animating()) return;
 | 
						|
	_a_show.stop();
 | 
						|
	_topShadow->setVisible(_peer ? true : false);
 | 
						|
	_historyToEnd->finishAnimation();
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::step_record(float64 ms, bool timer) {
 | 
						|
	float64 dt = ms / st::btnSend.duration;
 | 
						|
	if (dt >= 1 || !_send.isHidden() || isBotStart() || isBlocked()) {
 | 
						|
		_a_record.stop();
 | 
						|
		a_recordOver.finish();
 | 
						|
		a_recordDown.finish();
 | 
						|
		a_recordCancel.finish();
 | 
						|
	} else {
 | 
						|
		a_recordOver.update(dt, anim::linear);
 | 
						|
		a_recordDown.update(dt, anim::linear);
 | 
						|
		a_recordCancel.update(dt, anim::linear);
 | 
						|
	}
 | 
						|
	if (timer) {
 | 
						|
		if (_recording) {
 | 
						|
			updateField();
 | 
						|
		} else {
 | 
						|
			update(_send.geometry());
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::step_recording(float64 ms, bool timer) {
 | 
						|
	float64 dt = ms / AudioVoiceMsgUpdateView;
 | 
						|
	if (dt >= 1) {
 | 
						|
		_a_recording.stop();
 | 
						|
		a_recordingLevel.finish();
 | 
						|
	} else {
 | 
						|
		a_recordingLevel.update(dt, anim::linear);
 | 
						|
	}
 | 
						|
	if (timer) update(_attachDocument.geometry());
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::onPhotoSelect() {
 | 
						|
	if (!_history) return;
 | 
						|
 | 
						|
	_attachDocument.clearState();
 | 
						|
	_attachDocument.hide();
 | 
						|
	_attachPhoto.show();
 | 
						|
	_attachType->fastHide();
 | 
						|
 | 
						|
	if (cDefaultAttach() != dbidaPhoto) {
 | 
						|
		cSetDefaultAttach(dbidaPhoto);
 | 
						|
		Local::writeUserSettings();
 | 
						|
	}
 | 
						|
 | 
						|
	QStringList photoExtensions(cPhotoExtensions());
 | 
						|
	QStringList imgExtensions(cImgExtensions());
 | 
						|
	QString filter(qsl("Image files (*") + imgExtensions.join(qsl(" *")) + qsl(");;Photo files (*") + photoExtensions.join(qsl(" *")) + qsl(");;") + filedialogAllFilesFilter());
 | 
						|
 | 
						|
	QStringList files;
 | 
						|
	QByteArray content;
 | 
						|
	if (filedialogGetOpenFiles(files, content, lang(lng_choose_images), filter)) {
 | 
						|
		if (!content.isEmpty()) {
 | 
						|
			uploadFileContent(content, PreparePhoto);
 | 
						|
		} else {
 | 
						|
			uploadFiles(files, PreparePhoto);
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::onDocumentSelect() {
 | 
						|
	if (!_history) return;
 | 
						|
 | 
						|
	_attachPhoto.clearState();
 | 
						|
	_attachPhoto.hide();
 | 
						|
	_attachDocument.show();
 | 
						|
	_attachType->fastHide();
 | 
						|
 | 
						|
	if (cDefaultAttach() != dbidaDocument) {
 | 
						|
		cSetDefaultAttach(dbidaDocument);
 | 
						|
		Local::writeUserSettings();
 | 
						|
	}
 | 
						|
 | 
						|
	QStringList photoExtensions(cPhotoExtensions());
 | 
						|
	QStringList imgExtensions(cImgExtensions());
 | 
						|
	QString filter(filedialogAllFilesFilter() + qsl(";;Image files (*") + imgExtensions.join(qsl(" *")) + qsl(");;Photo files (*") + photoExtensions.join(qsl(" *")) + qsl(")"));
 | 
						|
 | 
						|
	QStringList files;
 | 
						|
	QByteArray content;
 | 
						|
	if (filedialogGetOpenFiles(files, content, lang(lng_choose_images), filter)) {
 | 
						|
		if (!content.isEmpty()) {
 | 
						|
			uploadFileContent(content, PrepareDocument);
 | 
						|
		} else {
 | 
						|
			uploadFiles(files, PrepareDocument);
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::dragEnterEvent(QDragEnterEvent *e) {
 | 
						|
	if (!_history) return;
 | 
						|
 | 
						|
	if (_peer && !_canSendMessages) return;
 | 
						|
 | 
						|
	_attachDrag = getDragState(e->mimeData());
 | 
						|
	updateDragAreas();
 | 
						|
 | 
						|
	if (_attachDrag) {
 | 
						|
		e->setDropAction(Qt::IgnoreAction);
 | 
						|
		e->accept();
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::dragLeaveEvent(QDragLeaveEvent *e) {
 | 
						|
	if (_attachDrag != DragStateNone || !_attachDragPhoto->isHidden() || !_attachDragDocument->isHidden()) {
 | 
						|
		_attachDrag = DragStateNone;
 | 
						|
		updateDragAreas();
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::leaveEvent(QEvent *e) {
 | 
						|
	if (_attachDrag != DragStateNone || !_attachDragPhoto->isHidden() || !_attachDragDocument->isHidden()) {
 | 
						|
		_attachDrag = DragStateNone;
 | 
						|
		updateDragAreas();
 | 
						|
	}
 | 
						|
	if (hasMouseTracking()) mouseMoveEvent(0);
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::mouseMoveEvent(QMouseEvent *e) {
 | 
						|
	QPoint pos(e ? e->pos() : mapFromGlobal(QCursor::pos()));
 | 
						|
	bool inRecord = _send.geometry().contains(pos);
 | 
						|
	bool inField = pos.y() >= (_scroll.y() + _scroll.height()) && pos.y() < height() && pos.x() >= 0 && pos.x() < width();
 | 
						|
	bool inReplyEdit = QRect(st::replySkip, _field.y() - st::sendPadding - st::replyHeight, width() - st::replySkip - _fieldBarCancel.width(), st::replyHeight).contains(pos) && (_editMsgId || replyToId());
 | 
						|
	bool inPinnedMsg = QRect(0, 0, width(), st::replyHeight).contains(pos) && _pinnedBar;
 | 
						|
	bool startAnim = false;
 | 
						|
	if (inRecord != _inRecord) {
 | 
						|
		_inRecord = inRecord;
 | 
						|
		a_recordOver.start(_inRecord ? 1 : 0);
 | 
						|
		a_recordDown.restart();
 | 
						|
		a_recordCancel.restart();
 | 
						|
		startAnim = true;
 | 
						|
	}
 | 
						|
	if (inField != _inField && _recording) {
 | 
						|
		_inField = inField;
 | 
						|
		a_recordOver.restart();
 | 
						|
		a_recordDown.start(_inField ? 1 : 0);
 | 
						|
		a_recordCancel.start(_inField ? st::recordCancel->c : st::recordCancelActive->c);
 | 
						|
		startAnim = true;
 | 
						|
	}
 | 
						|
	if (inReplyEdit != _inReplyEdit) {
 | 
						|
		_inReplyEdit = inReplyEdit;
 | 
						|
		setCursor(inReplyEdit ? style::cur_pointer : style::cur_default);
 | 
						|
	}
 | 
						|
	if (inPinnedMsg != _inPinnedMsg) {
 | 
						|
		_inPinnedMsg = inPinnedMsg;
 | 
						|
		setCursor(inPinnedMsg ? style::cur_pointer : style::cur_default);
 | 
						|
	}
 | 
						|
	if (startAnim) _a_record.start();
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::leaveToChildEvent(QEvent *e, QWidget *child) { // e -- from enterEvent() of child TWidget
 | 
						|
	if (hasMouseTracking()) mouseMoveEvent(0);
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::mouseReleaseEvent(QMouseEvent *e) {
 | 
						|
	if (_replyForwardPressed) {
 | 
						|
		_replyForwardPressed = false;
 | 
						|
		update(0, _field.y() - st::sendPadding - st::replyHeight, width(), st::replyHeight);
 | 
						|
	}
 | 
						|
	if (_attachDrag != DragStateNone || !_attachDragPhoto->isHidden() || !_attachDragDocument->isHidden()) {
 | 
						|
		_attachDrag = DragStateNone;
 | 
						|
		updateDragAreas();
 | 
						|
	}
 | 
						|
	if (_recording && cHasAudioCapture()) {
 | 
						|
		stopRecording(_peer && _inField);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::stopRecording(bool send) {
 | 
						|
	emit audioCapture()->stop(send);
 | 
						|
 | 
						|
	a_recordingLevel = anim::ivalue(0, 0);
 | 
						|
	_a_recording.stop();
 | 
						|
 | 
						|
	_recording = false;
 | 
						|
	_recordingSamples = 0;
 | 
						|
	if (_peer && (!_peer->isChannel() || _peer->isMegagroup())) {
 | 
						|
		updateSendAction(_history, SendActionRecordVoice, -1);
 | 
						|
	}
 | 
						|
 | 
						|
	updateControlsVisibility();
 | 
						|
	activate();
 | 
						|
 | 
						|
	updateField();
 | 
						|
 | 
						|
	a_recordDown.start(0);
 | 
						|
	a_recordOver.restart();
 | 
						|
	a_recordCancel = anim::cvalue(st::recordCancel->c, st::recordCancel->c);
 | 
						|
	_a_record.start();
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::sendBotCommand(PeerData *peer, UserData *bot, const QString &cmd, MsgId replyTo) { // replyTo != 0 from ReplyKeyboardMarkup, == 0 from cmd links
 | 
						|
	if (!_peer || _peer != peer) return;
 | 
						|
 | 
						|
	bool lastKeyboardUsed = (_keyboard.forMsgId() == FullMsgId(_channel, _history->lastKeyboardId)) && (_keyboard.forMsgId() == FullMsgId(_channel, replyTo));
 | 
						|
 | 
						|
	QString toSend = cmd;
 | 
						|
	if (bot && (!bot->isUser() || !bot->asUser()->botInfo)) {
 | 
						|
		bot = nullptr;
 | 
						|
	}
 | 
						|
	QString username = bot ? bot->asUser()->username : QString();
 | 
						|
	int32 botStatus = _peer->isChat() ? _peer->asChat()->botStatus : (_peer->isMegagroup() ? _peer->asChannel()->mgInfo->botStatus : -1);
 | 
						|
	if (!replyTo && toSend.indexOf('@') < 2 && !username.isEmpty() && (botStatus == 0 || botStatus == 2)) {
 | 
						|
		toSend += '@' + username;
 | 
						|
	}
 | 
						|
 | 
						|
	MainWidget::MessageToSend message;
 | 
						|
	message.history = _history;
 | 
						|
	message.textWithTags = { toSend, TextWithTags::Tags() };
 | 
						|
	message.replyTo = replyTo ? ((!_peer->isUser()/* && (botStatus == 0 || botStatus == 2)*/) ? replyTo : -1) : 0;
 | 
						|
	message.silent = false;
 | 
						|
	App::main()->sendMessage(message);
 | 
						|
	if (replyTo) {
 | 
						|
		if (_replyToId == replyTo) {
 | 
						|
			cancelReply();
 | 
						|
			onCloudDraftSave();
 | 
						|
		}
 | 
						|
		if (_keyboard.singleUse() && _keyboard.hasMarkup() && lastKeyboardUsed) {
 | 
						|
			if (_kbShown) onKbToggle(false);
 | 
						|
			_history->lastKeyboardUsed = true;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	_field.setFocus();
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::app_sendBotCallback(const HistoryMessageReplyMarkup::Button *button, const HistoryItem *msg, int row, int col) {
 | 
						|
	if (msg->id < 0 || _peer != msg->history()->peer) {
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	bool lastKeyboardUsed = (_keyboard.forMsgId() == FullMsgId(_channel, _history->lastKeyboardId)) && (_keyboard.forMsgId() == FullMsgId(_channel, msg->id));
 | 
						|
 | 
						|
	auto bot = msg->getMessageBot();
 | 
						|
 | 
						|
	using ButtonType = HistoryMessageReplyMarkup::Button::Type;
 | 
						|
	BotCallbackInfo info = { bot, msg->fullId(), row, col, (button->type == ButtonType::Game) };
 | 
						|
	MTPmessages_GetBotCallbackAnswer::Flags flags = 0;
 | 
						|
	QByteArray sendData;
 | 
						|
	if (info.game) {
 | 
						|
		flags = MTPmessages_GetBotCallbackAnswer::Flag::f_game;
 | 
						|
	} else if (button->type == ButtonType::Callback) {
 | 
						|
		flags = MTPmessages_GetBotCallbackAnswer::Flag::f_data;
 | 
						|
		sendData = button->data;
 | 
						|
	}
 | 
						|
	button->requestId = MTP::send(MTPmessages_GetBotCallbackAnswer(MTP_flags(flags), _peer->input, MTP_int(msg->id), MTP_bytes(sendData)), rpcDone(&HistoryWidget::botCallbackDone, info), rpcFail(&HistoryWidget::botCallbackFail, info));
 | 
						|
	Ui::repaintHistoryItem(msg);
 | 
						|
 | 
						|
	if (_replyToId == msg->id) {
 | 
						|
		cancelReply();
 | 
						|
	}
 | 
						|
	if (_keyboard.singleUse() && _keyboard.hasMarkup() && lastKeyboardUsed) {
 | 
						|
		if (_kbShown) onKbToggle(false);
 | 
						|
		_history->lastKeyboardUsed = true;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::botCallbackDone(BotCallbackInfo info, const MTPmessages_BotCallbackAnswer &answer, mtpRequestId req) {
 | 
						|
	auto item = App::histItemById(info.msgId);
 | 
						|
	if (item) {
 | 
						|
		if (auto markup = item->Get<HistoryMessageReplyMarkup>()) {
 | 
						|
			if (info.row < markup->rows.size() && info.col < markup->rows.at(info.row).size()) {
 | 
						|
				if (markup->rows.at(info.row).at(info.col).requestId == req) {
 | 
						|
					markup->rows.at(info.row).at(info.col).requestId = 0;
 | 
						|
					Ui::repaintHistoryItem(item);
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if (answer.type() == mtpc_messages_botCallbackAnswer) {
 | 
						|
		auto &answerData = answer.c_messages_botCallbackAnswer();
 | 
						|
		if (answerData.has_message()) {
 | 
						|
			if (answerData.is_alert()) {
 | 
						|
				Ui::showLayer(new InformBox(qs(answerData.vmessage)));
 | 
						|
			} else if (App::wnd()) {
 | 
						|
				Ui::Toast::Config toast;
 | 
						|
				toast.text = qs(answerData.vmessage);
 | 
						|
				Ui::Toast::Show(App::wnd(), toast);
 | 
						|
			}
 | 
						|
		} else if (answerData.has_url()) {
 | 
						|
			auto url = qs(answerData.vurl);
 | 
						|
			if (info.game) {
 | 
						|
				url = appendShareGameScoreUrl(url, info.msgId);
 | 
						|
				BotGameUrlClickHandler(info.bot, url).onClick(Qt::LeftButton);
 | 
						|
				if (item && (!item->history()->peer->isChannel() || item->history()->peer->isMegagroup())) {
 | 
						|
					updateSendAction(item->history(), SendActionPlayGame);
 | 
						|
				}
 | 
						|
			} else {
 | 
						|
				UrlClickHandler(url).onClick(Qt::LeftButton);
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
bool HistoryWidget::botCallbackFail(BotCallbackInfo info, const RPCError &error, mtpRequestId req) {
 | 
						|
	// show error?
 | 
						|
	if (HistoryItem *item = App::histItemById(info.msgId)) {
 | 
						|
		if (auto markup = item->Get<HistoryMessageReplyMarkup>()) {
 | 
						|
			if (info.row < markup->rows.size() && info.col < markup->rows.at(info.row).size()) {
 | 
						|
				if (markup->rows.at(info.row).at(info.col).requestId == req) {
 | 
						|
					markup->rows.at(info.row).at(info.col).requestId = 0;
 | 
						|
					Ui::repaintHistoryItem(item);
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return true;
 | 
						|
}
 | 
						|
 | 
						|
bool HistoryWidget::insertBotCommand(const QString &cmd, bool specialGif) {
 | 
						|
	if (!_history || !canWriteMessage()) return false;
 | 
						|
 | 
						|
	bool insertingInlineBot = !cmd.isEmpty() && (cmd.at(0) == '@');
 | 
						|
	QString toInsert = cmd;
 | 
						|
	if (!toInsert.isEmpty() && !insertingInlineBot) {
 | 
						|
		PeerData *bot = _peer->isUser() ? _peer : (App::hoveredLinkItem() ? App::hoveredLinkItem()->fromOriginal() : 0);
 | 
						|
		if (!bot->isUser() || !bot->asUser()->botInfo) bot = 0;
 | 
						|
		QString username = bot ? bot->asUser()->username : QString();
 | 
						|
		int32 botStatus = _peer->isChat() ? _peer->asChat()->botStatus : (_peer->isMegagroup() ? _peer->asChannel()->mgInfo->botStatus : -1);
 | 
						|
		if (toInsert.indexOf('@') < 0 && !username.isEmpty() && (botStatus == 0 || botStatus == 2)) {
 | 
						|
			toInsert += '@' + username;
 | 
						|
		}
 | 
						|
	}
 | 
						|
	toInsert += ' ';
 | 
						|
 | 
						|
	if (!insertingInlineBot) {
 | 
						|
		auto &textWithTags = _field.getTextWithTags();
 | 
						|
		if (specialGif) {
 | 
						|
			if (textWithTags.text.trimmed() == '@' + cInlineGifBotUsername() && textWithTags.text.at(0) == '@') {
 | 
						|
				clearFieldText(TextUpdateEvent::SaveDraft, FlatTextarea::AddToUndoHistory);
 | 
						|
			}
 | 
						|
		} else {
 | 
						|
			TextWithTags textWithTagsToSet;
 | 
						|
			QRegularExpressionMatch m = QRegularExpression(qsl("^/[A-Za-z_0-9]{0,64}(@[A-Za-z_0-9]{0,32})?(\\s|$)")).match(textWithTags.text);
 | 
						|
			if (m.hasMatch()) {
 | 
						|
				textWithTagsToSet = _field.getTextWithTagsPart(m.capturedLength());
 | 
						|
			} else {
 | 
						|
				textWithTagsToSet = textWithTags;
 | 
						|
			}
 | 
						|
			textWithTagsToSet.text = toInsert + textWithTagsToSet.text;
 | 
						|
			for (auto &tag : textWithTagsToSet.tags) {
 | 
						|
				tag.offset += toInsert.size();
 | 
						|
			}
 | 
						|
			_field.setTextWithTags(textWithTagsToSet);
 | 
						|
 | 
						|
			QTextCursor cur(_field.textCursor());
 | 
						|
			cur.movePosition(QTextCursor::End);
 | 
						|
			_field.setTextCursor(cur);
 | 
						|
		}
 | 
						|
	} else {
 | 
						|
		if (!specialGif || _field.isEmpty()) {
 | 
						|
			setFieldText({ toInsert, TextWithTags::Tags() }, TextUpdateEvent::SaveDraft, FlatTextarea::AddToUndoHistory);
 | 
						|
			_field.setFocus();
 | 
						|
			return true;
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return false;
 | 
						|
}
 | 
						|
 | 
						|
bool HistoryWidget::eventFilter(QObject *obj, QEvent *e) {
 | 
						|
	if (obj == _historyToEnd && e->type() == QEvent::Wheel) {
 | 
						|
		return _scroll.viewportEvent(e);
 | 
						|
	}
 | 
						|
	return TWidget::eventFilter(obj, e);
 | 
						|
}
 | 
						|
 | 
						|
DragState HistoryWidget::getDragState(const QMimeData *d) {
 | 
						|
	if (!d
 | 
						|
		|| d->hasFormat(qsl("application/x-td-forward-selected"))
 | 
						|
		|| d->hasFormat(qsl("application/x-td-forward-pressed"))
 | 
						|
		|| d->hasFormat(qsl("application/x-td-forward-pressed-link"))) return DragStateNone;
 | 
						|
 | 
						|
	if (d->hasImage()) return DragStateImage;
 | 
						|
 | 
						|
	QString uriListFormat(qsl("text/uri-list"));
 | 
						|
	if (!d->hasFormat(uriListFormat)) return DragStateNone;
 | 
						|
 | 
						|
	QStringList imgExtensions(cImgExtensions()), files;
 | 
						|
 | 
						|
	const QList<QUrl> &urls(d->urls());
 | 
						|
	if (urls.isEmpty()) return DragStateNone;
 | 
						|
 | 
						|
	bool allAreSmallImages = true;
 | 
						|
	for (QList<QUrl>::const_iterator i = urls.cbegin(), en = urls.cend(); i != en; ++i) {
 | 
						|
		if (!i->isLocalFile()) return DragStateNone;
 | 
						|
 | 
						|
		auto file = psConvertFileUrl(*i);
 | 
						|
 | 
						|
		QFileInfo info(file);
 | 
						|
		if (info.isDir()) return DragStateNone;
 | 
						|
 | 
						|
		quint64 s = info.size();
 | 
						|
		if (s >= MaxUploadDocumentSize) {
 | 
						|
			return DragStateNone;
 | 
						|
		}
 | 
						|
		if (allAreSmallImages) {
 | 
						|
			if (s >= MaxUploadPhotoSize) {
 | 
						|
				allAreSmallImages = false;
 | 
						|
			} else {
 | 
						|
				bool foundImageExtension = false;
 | 
						|
				for (QStringList::const_iterator j = imgExtensions.cbegin(), end = imgExtensions.cend(); j != end; ++j) {
 | 
						|
					if (file.right(j->size()).toLower() == (*j).toLower()) {
 | 
						|
						foundImageExtension = true;
 | 
						|
						break;
 | 
						|
					}
 | 
						|
				}
 | 
						|
				if (!foundImageExtension) {
 | 
						|
					allAreSmallImages = false;
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return allAreSmallImages ? DragStatePhotoFiles : DragStateFiles;
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::updateDragAreas() {
 | 
						|
	_field.setAcceptDrops(!_attachDrag);
 | 
						|
	switch (_attachDrag) {
 | 
						|
	case DragStateNone:
 | 
						|
		_attachDragDocument->otherLeave();
 | 
						|
		_attachDragPhoto->otherLeave();
 | 
						|
	break;
 | 
						|
	case DragStateFiles:
 | 
						|
		_attachDragDocument->otherEnter();
 | 
						|
		_attachDragDocument->setText(lang(lng_drag_files_here), lang(lng_drag_to_send_files));
 | 
						|
		_attachDragPhoto->fastHide();
 | 
						|
	break;
 | 
						|
	case DragStatePhotoFiles:
 | 
						|
		_attachDragDocument->otherEnter();
 | 
						|
		_attachDragDocument->setText(lang(lng_drag_images_here), lang(lng_drag_to_send_no_compression));
 | 
						|
		_attachDragPhoto->otherEnter();
 | 
						|
		_attachDragPhoto->setText(lang(lng_drag_photos_here), lang(lng_drag_to_send_quick));
 | 
						|
	break;
 | 
						|
	case DragStateImage:
 | 
						|
		_attachDragDocument->fastHide();
 | 
						|
		_attachDragPhoto->otherEnter();
 | 
						|
		_attachDragPhoto->setText(lang(lng_drag_images_here), lang(lng_drag_to_send_quick));
 | 
						|
	break;
 | 
						|
	};
 | 
						|
	resizeEvent(0);
 | 
						|
}
 | 
						|
 | 
						|
bool HistoryWidget::canSendMessages(PeerData *peer) const {
 | 
						|
	return peer && peer->canWrite();
 | 
						|
}
 | 
						|
 | 
						|
bool HistoryWidget::readyToForward() const {
 | 
						|
	return _canSendMessages && App::main()->hasForwardingItems();
 | 
						|
}
 | 
						|
 | 
						|
bool HistoryWidget::hasSilentToggle() const {
 | 
						|
	return _peer && _peer->isChannel() && !_peer->isMegagroup() && _peer->asChannel()->canPublish() && _peer->notify != UnknownNotifySettings;
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::inlineBotResolveDone(const MTPcontacts_ResolvedPeer &result) {
 | 
						|
	_inlineBotResolveRequestId = 0;
 | 
						|
//	Notify::inlineBotRequesting(false);
 | 
						|
	UserData *resolvedBot = nullptr;
 | 
						|
	if (result.type() == mtpc_contacts_resolvedPeer) {
 | 
						|
		const auto &d(result.c_contacts_resolvedPeer());
 | 
						|
		resolvedBot = App::feedUsers(d.vusers);
 | 
						|
		if (resolvedBot) {
 | 
						|
			if (!resolvedBot->botInfo || resolvedBot->botInfo->inlinePlaceholder.isEmpty()) {
 | 
						|
				resolvedBot = nullptr;
 | 
						|
			}
 | 
						|
		}
 | 
						|
		App::feedChats(d.vchats);
 | 
						|
	}
 | 
						|
 | 
						|
	UserData *bot = nullptr;
 | 
						|
	QString inlineBotUsername;
 | 
						|
	auto query = _field.getInlineBotQuery(&bot, &inlineBotUsername);
 | 
						|
	if (inlineBotUsername == _inlineBotUsername) {
 | 
						|
		if (bot == LookingUpInlineBot) {
 | 
						|
			bot = resolvedBot;
 | 
						|
		}
 | 
						|
	} else {
 | 
						|
		bot = nullptr;
 | 
						|
	}
 | 
						|
	if (bot) {
 | 
						|
		applyInlineBotQuery(bot, query);
 | 
						|
	} else {
 | 
						|
		clearInlineBot();
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
bool HistoryWidget::inlineBotResolveFail(QString name, const RPCError &error) {
 | 
						|
	if (MTP::isDefaultHandledError(error)) return false;
 | 
						|
 | 
						|
	_inlineBotResolveRequestId = 0;
 | 
						|
//	Notify::inlineBotRequesting(false);
 | 
						|
	if (name == _inlineBotUsername) {
 | 
						|
		clearInlineBot();
 | 
						|
	}
 | 
						|
	return true;
 | 
						|
}
 | 
						|
 | 
						|
bool HistoryWidget::isBotStart() const {
 | 
						|
	if (!_peer || !_peer->isUser() || !_peer->asUser()->botInfo || !_canSendMessages) return false;
 | 
						|
	return !_peer->asUser()->botInfo->startToken.isEmpty() || (_history->isEmpty() && !_history->lastMsg);
 | 
						|
}
 | 
						|
 | 
						|
bool HistoryWidget::isBlocked() const {
 | 
						|
	return _peer && _peer->isUser() && _peer->asUser()->isBlocked();
 | 
						|
}
 | 
						|
 | 
						|
bool HistoryWidget::isJoinChannel() const {
 | 
						|
	return _peer && _peer->isChannel() && !_peer->asChannel()->amIn();
 | 
						|
}
 | 
						|
 | 
						|
bool HistoryWidget::isMuteUnmute() const {
 | 
						|
	return _peer && _peer->isChannel() && _peer->asChannel()->isBroadcast() && !_peer->asChannel()->canPublish();
 | 
						|
}
 | 
						|
 | 
						|
bool HistoryWidget::updateCmdStartShown() {
 | 
						|
	bool cmdStartShown = false;
 | 
						|
	if (_history && _peer && ((_peer->isChat() && _peer->asChat()->botStatus > 0) || (_peer->isMegagroup() && _peer->asChannel()->mgInfo->botStatus > 0) || (_peer->isUser() && _peer->asUser()->botInfo))) {
 | 
						|
		if (!isBotStart() && !isBlocked() && !_keyboard.hasMarkup() && !_keyboard.forceReply()) {
 | 
						|
			if (!_field.hasSendText()) {
 | 
						|
				cmdStartShown = true;
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if (_cmdStartShown != cmdStartShown) {
 | 
						|
		_cmdStartShown = cmdStartShown;
 | 
						|
		return true;
 | 
						|
	}
 | 
						|
	return false;
 | 
						|
}
 | 
						|
 | 
						|
bool HistoryWidget::kbWasHidden() const {
 | 
						|
	return _history && (_keyboard.forMsgId() == FullMsgId(_history->channelId(), _history->lastKeyboardHiddenId));
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::dropEvent(QDropEvent *e) {
 | 
						|
	_attachDrag = DragStateNone;
 | 
						|
	updateDragAreas();
 | 
						|
	e->acceptProposedAction();
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::onPhotoDrop(const QMimeData *data) {
 | 
						|
	if (!_history) return;
 | 
						|
 | 
						|
	QStringList files = getMediasFromMime(data);
 | 
						|
	if (files.isEmpty()) {
 | 
						|
		if (data->hasImage()) {
 | 
						|
			QImage image = qvariant_cast<QImage>(data->imageData());
 | 
						|
			if (image.isNull()) return;
 | 
						|
 | 
						|
			uploadImage(image, PreparePhoto, FileLoadNoForceConfirm, data->text());
 | 
						|
		}
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	uploadFiles(files, PreparePhoto);
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::onDocumentDrop(const QMimeData *data) {
 | 
						|
	if (!_history) return;
 | 
						|
 | 
						|
	if (_peer && !_canSendMessages) return;
 | 
						|
 | 
						|
	QStringList files = getMediasFromMime(data);
 | 
						|
	if (files.isEmpty()) return;
 | 
						|
 | 
						|
	uploadFiles(files, PrepareDocument);
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::onFilesDrop(const QMimeData *data) {
 | 
						|
 | 
						|
	if (_peer && !_canSendMessages) return;
 | 
						|
 | 
						|
	QStringList files = getMediasFromMime(data);
 | 
						|
	if (files.isEmpty()) {
 | 
						|
		if (data->hasImage()) {
 | 
						|
			QImage image = qvariant_cast<QImage>(data->imageData());
 | 
						|
			if (image.isNull()) return;
 | 
						|
 | 
						|
			uploadImage(image, PrepareAuto, FileLoadNoForceConfirm, data->text());
 | 
						|
		}
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	if (files.size() == 1 && !QFileInfo(files.at(0)).isDir()) {
 | 
						|
		uploadFile(files.at(0), PrepareAuto);
 | 
						|
	}
 | 
						|
//  uploadFiles(files, PrepareAuto); // multiple confirm with "compressed" checkbox
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::onKbToggle(bool manual) {
 | 
						|
	auto fieldEnabled = canWriteMessage();
 | 
						|
	if (_kbShown || _kbReplyTo) {
 | 
						|
		_kbHide.hide();
 | 
						|
		if (_kbShown) {
 | 
						|
			if (fieldEnabled) {
 | 
						|
				_kbShow.show();
 | 
						|
			}
 | 
						|
			if (manual && _history) {
 | 
						|
				_history->lastKeyboardHiddenId = _keyboard.forMsgId().msg;
 | 
						|
			}
 | 
						|
 | 
						|
			_kbScroll.hide();
 | 
						|
			_kbShown = false;
 | 
						|
 | 
						|
			_field.setMaxHeight(st::maxFieldHeight);
 | 
						|
 | 
						|
			_kbReplyTo = 0;
 | 
						|
			if (!readyToForward() && (!_previewData || _previewData->pendingTill < 0) && !_editMsgId && !_replyToId) {
 | 
						|
				_fieldBarCancel.hide();
 | 
						|
				updateMouseTracking();
 | 
						|
			}
 | 
						|
		} else {
 | 
						|
			if (_history) {
 | 
						|
				_history->clearLastKeyboard();
 | 
						|
			} else {
 | 
						|
				updateBotKeyboard();
 | 
						|
			}
 | 
						|
		}
 | 
						|
	} else if (!_keyboard.hasMarkup() && _keyboard.forceReply()) {
 | 
						|
		_kbHide.hide();
 | 
						|
		_kbShow.hide();
 | 
						|
		if (fieldEnabled) {
 | 
						|
			_cmdStart.show();
 | 
						|
		}
 | 
						|
		_kbScroll.hide();
 | 
						|
		_kbShown = false;
 | 
						|
 | 
						|
		_field.setMaxHeight(st::maxFieldHeight);
 | 
						|
 | 
						|
		_kbReplyTo = (_peer->isChat() || _peer->isChannel() || _keyboard.forceReply()) ? App::histItemById(_keyboard.forMsgId()) : 0;
 | 
						|
		if (_kbReplyTo && !_editMsgId && !_replyToId && fieldEnabled) {
 | 
						|
			updateReplyToName();
 | 
						|
			_replyEditMsgText.setText(st::msgFont, textClean(_kbReplyTo->inReplyText()), _textDlgOptions);
 | 
						|
			_fieldBarCancel.show();
 | 
						|
			updateMouseTracking();
 | 
						|
		}
 | 
						|
		if (manual && _history) {
 | 
						|
			_history->lastKeyboardHiddenId = 0;
 | 
						|
		}
 | 
						|
	} else if (fieldEnabled) {
 | 
						|
		_kbHide.show();
 | 
						|
		_kbShow.hide();
 | 
						|
		_kbScroll.show();
 | 
						|
		_kbShown = true;
 | 
						|
 | 
						|
		int32 maxh = qMin(_keyboard.height(), int(st::maxFieldHeight) - (int(st::maxFieldHeight) / 2));
 | 
						|
		_field.setMaxHeight(st::maxFieldHeight - maxh);
 | 
						|
 | 
						|
		_kbReplyTo = (_peer->isChat() || _peer->isChannel() || _keyboard.forceReply()) ? App::histItemById(_keyboard.forMsgId()) : 0;
 | 
						|
		if (_kbReplyTo && !_editMsgId && !_replyToId) {
 | 
						|
			updateReplyToName();
 | 
						|
			_replyEditMsgText.setText(st::msgFont, textClean(_kbReplyTo->inReplyText()), _textDlgOptions);
 | 
						|
			_fieldBarCancel.show();
 | 
						|
			updateMouseTracking();
 | 
						|
		}
 | 
						|
		if (manual && _history) {
 | 
						|
			_history->lastKeyboardHiddenId = 0;
 | 
						|
		}
 | 
						|
	}
 | 
						|
	resizeEvent(0);
 | 
						|
	if (_kbHide.isHidden() && canWriteMessage()) {
 | 
						|
		_attachEmoji.show();
 | 
						|
	} else {
 | 
						|
		_attachEmoji.hide();
 | 
						|
	}
 | 
						|
	updateField();
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::onCmdStart() {
 | 
						|
	setFieldText({ qsl("/"), TextWithTags::Tags() }, 0, FlatTextarea::AddToUndoHistory);
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::contextMenuEvent(QContextMenuEvent *e) {
 | 
						|
	if (!_list) return;
 | 
						|
 | 
						|
	return _list->showContextMenu(e);
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::deleteMessage() {
 | 
						|
	HistoryItem *item = App::contextItem();
 | 
						|
	if (!item || item->type() != HistoryItemMsg) return;
 | 
						|
 | 
						|
	HistoryMessage *msg = dynamic_cast<HistoryMessage*>(item);
 | 
						|
	App::main()->deleteLayer((msg && msg->uploading()) ? -2 : -1);
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::forwardMessage() {
 | 
						|
	HistoryItem *item = App::contextItem();
 | 
						|
	if (!item || item->type() != HistoryItemMsg || item->serviceMsg()) return;
 | 
						|
 | 
						|
	App::main()->forwardLayer();
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::selectMessage() {
 | 
						|
	HistoryItem *item = App::contextItem();
 | 
						|
	if (!item || item->type() != HistoryItemMsg || item->serviceMsg()) return;
 | 
						|
 | 
						|
	if (_list) _list->selectItem(item);
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::onForwardHere() {
 | 
						|
	HistoryItem *item = App::contextItem();
 | 
						|
	if (!item || item->type() != HistoryItemMsg || item->serviceMsg()) return;
 | 
						|
 | 
						|
	App::forward(_peer->id, ForwardContextMessage);
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::paintTopBar(Painter &p, float64 over, int32 decreaseWidth) {
 | 
						|
	if (_a_show.animating()) {
 | 
						|
		int retina = cIntRetinaFactor();
 | 
						|
		if (a_coordOver.current() > 0) {
 | 
						|
			p.drawPixmap(QRect(0, 0, a_coordOver.current(), st::topBarHeight), _cacheUnder, QRect(-a_coordUnder.current() * retina, 0, a_coordOver.current() * retina, st::topBarHeight * retina));
 | 
						|
			p.setOpacity(a_progress.current() * st::slideFadeOut);
 | 
						|
			p.fillRect(0, 0, a_coordOver.current(), st::topBarHeight, st::black);
 | 
						|
			p.setOpacity(1);
 | 
						|
		}
 | 
						|
		p.drawPixmap(QRect(a_coordOver.current(), 0, _cacheOver.width() / retina, st::topBarHeight), _cacheOver, QRect(0, 0, _cacheOver.width(), st::topBarHeight * retina));
 | 
						|
		p.setOpacity(a_progress.current());
 | 
						|
		p.drawPixmap(QRect(a_coordOver.current() - st::slideShadow.pxWidth(), 0, st::slideShadow.pxWidth(), st::topBarHeight), App::sprite(), st::slideShadow.rect());
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	if (!_history) return;
 | 
						|
 | 
						|
	int32 increaseLeft = (Adaptive::OneColumn() || !App::main()->stackIsEmpty()) ? (st::topBarForwardPadding.right() - st::topBarForwardPadding.left()) : 0;
 | 
						|
	decreaseWidth += increaseLeft;
 | 
						|
	QRect rectForName(st::topBarForwardPadding.left() + increaseLeft, st::topBarForwardPadding.top(), width() - decreaseWidth - st::topBarForwardPadding.left() - st::topBarForwardPadding.right(), st::msgNameFont->height);
 | 
						|
	p.setFont(st::dialogsTextFont);
 | 
						|
	if (_history->typing.isEmpty() && _history->sendActions.isEmpty()) {
 | 
						|
		p.setPen(_titlePeerTextOnline ? st::titleStatusActiveFg : st::titleStatusFg);
 | 
						|
		p.drawText(rectForName.x(), st::topBarHeight - st::topBarForwardPadding.bottom() - st::dialogsTextFont->height + st::dialogsTextFont->ascent, _titlePeerText);
 | 
						|
	} else {
 | 
						|
		p.setPen(st::titleTypingFg);
 | 
						|
		_history->typingText.drawElided(p, rectForName.x(), st::topBarHeight - st::topBarForwardPadding.bottom() - st::dialogsTextFont->height, rectForName.width());
 | 
						|
	}
 | 
						|
 | 
						|
	p.setPen(st::dialogsNameFg);
 | 
						|
	_peer->dialogName().drawElided(p, rectForName.left(), rectForName.top(), rectForName.width());
 | 
						|
 | 
						|
	if (Adaptive::OneColumn() || !App::main()->stackIsEmpty()) {
 | 
						|
		p.setOpacity(st::topBarForwardAlpha + (1 - st::topBarForwardAlpha) * over);
 | 
						|
		p.drawSprite(QPoint((st::topBarForwardPadding.right() - st::topBarBackwardImg.pxWidth()) / 2, (st::topBarHeight - st::topBarBackwardImg.pxHeight()) / 2), st::topBarBackwardImg);
 | 
						|
	} else {
 | 
						|
		p.setOpacity(st::topBarForwardAlpha + (1 - st::topBarForwardAlpha) * over);
 | 
						|
		p.drawSprite(QPoint(width() - (st::topBarForwardPadding.right() + st::topBarForwardImg.pxWidth()) / 2, (st::topBarHeight - st::topBarForwardImg.pxHeight()) / 2), st::topBarForwardImg);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
QRect HistoryWidget::getMembersShowAreaGeometry() const {
 | 
						|
	int increaseLeft = (Adaptive::OneColumn() || !App::main()->stackIsEmpty()) ? (st::topBarForwardPadding.right() - st::topBarForwardPadding.left()) : 0;
 | 
						|
	int membersTextLeft = st::topBarForwardPadding.left() + increaseLeft;
 | 
						|
	int membersTextTop = st::topBarHeight - st::topBarForwardPadding.bottom() - st::dialogsTextFont->height;
 | 
						|
	int membersTextWidth = _titlePeerTextWidth;
 | 
						|
	int membersTextHeight = st::topBarHeight - membersTextTop;
 | 
						|
 | 
						|
	return rtlrect(membersTextLeft, membersTextTop, membersTextWidth, membersTextHeight, width());
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::setMembersShowAreaActive(bool active) {
 | 
						|
	if (!active) {
 | 
						|
		_membersDropdownShowTimer.stop();
 | 
						|
	}
 | 
						|
	if (active && _peer && (_peer->isChat() || _peer->isMegagroup())) {
 | 
						|
		if (_membersDropdown) {
 | 
						|
			_membersDropdown->otherEnter();
 | 
						|
		} else if (!_membersDropdownShowTimer.isActive()) {
 | 
						|
			_membersDropdownShowTimer.start(300);
 | 
						|
		}
 | 
						|
	} else if (_membersDropdown) {
 | 
						|
		_membersDropdown->otherLeave();
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::onMembersDropdownShow() {
 | 
						|
	if (!_membersDropdown) {
 | 
						|
		_membersDropdown.create(this, st::membersInnerDropdown, st::membersInnerScroll);
 | 
						|
		_membersDropdown->setOwnedWidget(new Profile::MembersWidget(_membersDropdown, _peer, Profile::MembersWidget::TitleVisibility::Hidden));
 | 
						|
		_membersDropdown->resize(st::membersInnerWidth, _membersDropdown->height());
 | 
						|
 | 
						|
		_membersDropdown->setMaxHeight(countMembersDropdownHeightMax());
 | 
						|
		_membersDropdown->moveToLeft(0, 0);
 | 
						|
		connect(_membersDropdown, SIGNAL(hidden()), this, SLOT(onMembersDropdownHidden()));
 | 
						|
	}
 | 
						|
	_membersDropdown->otherEnter();
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::onModerateKeyActivate(int index, bool *outHandled) {
 | 
						|
	*outHandled = _keyboard.isHidden() ? false : _keyboard.moderateKeyActivate(index);
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::onMembersDropdownHidden() {
 | 
						|
	_membersDropdown.destroyDelayed();
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::topBarClick() {
 | 
						|
	if (Adaptive::OneColumn() || !App::main()->stackIsEmpty()) {
 | 
						|
		App::main()->showBackFromStack();
 | 
						|
	} else {
 | 
						|
		if (_history) Ui::showPeerProfile(_peer);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::updateOnlineDisplay() {
 | 
						|
	if (!_history) return;
 | 
						|
 | 
						|
	QString text;
 | 
						|
	int32 t = unixtime();
 | 
						|
	bool titlePeerTextOnline = false;
 | 
						|
	if (auto user = _peer->asUser()) {
 | 
						|
		text = App::onlineText(user, t);
 | 
						|
		titlePeerTextOnline = App::onlineColorUse(user, t);
 | 
						|
	} else if (_peer->isChat()) {
 | 
						|
		ChatData *chat = _peer->asChat();
 | 
						|
		if (!chat->amIn()) {
 | 
						|
			text = lang(lng_chat_status_unaccessible);
 | 
						|
		} else if (chat->participants.isEmpty()) {
 | 
						|
			text = _titlePeerText.isEmpty() ? lng_chat_status_members(lt_count, chat->count < 0 ? 0 : chat->count) : _titlePeerText;
 | 
						|
		} else {
 | 
						|
			int32 onlineCount = 0;
 | 
						|
			bool onlyMe = true;
 | 
						|
			for (ChatData::Participants::const_iterator i = chat->participants.cbegin(), e = chat->participants.cend(); i != e; ++i) {
 | 
						|
				if (i.key()->onlineTill > t) {
 | 
						|
					++onlineCount;
 | 
						|
					if (onlyMe && i.key() != App::self()) onlyMe = false;
 | 
						|
				}
 | 
						|
			}
 | 
						|
			if (onlineCount && !onlyMe) {
 | 
						|
				text = lng_chat_status_members_online(lt_count, chat->participants.size(), lt_count_online, onlineCount);
 | 
						|
			} else {
 | 
						|
				text = lng_chat_status_members(lt_count, chat->participants.size());
 | 
						|
			}
 | 
						|
		}
 | 
						|
	} else if (_peer->isChannel()) {
 | 
						|
		if (_peer->isMegagroup() && _peer->asChannel()->membersCount() > 0 && _peer->asChannel()->membersCount() <= Global::ChatSizeMax()) {
 | 
						|
			if (_peer->asChannel()->mgInfo->lastParticipants.size() < _peer->asChannel()->membersCount() || _peer->asChannel()->lastParticipantsCountOutdated()) {
 | 
						|
				if (App::api()) App::api()->requestLastParticipants(_peer->asChannel());
 | 
						|
			}
 | 
						|
			int32 onlineCount = 0;
 | 
						|
			bool onlyMe = true;
 | 
						|
			for (auto i = _peer->asChannel()->mgInfo->lastParticipants.cbegin(), e = _peer->asChannel()->mgInfo->lastParticipants.cend(); i != e; ++i) {
 | 
						|
				if ((*i)->onlineTill > t) {
 | 
						|
					++onlineCount;
 | 
						|
					if (onlyMe && (*i) != App::self()) onlyMe = false;
 | 
						|
				}
 | 
						|
			}
 | 
						|
			if (onlineCount && !onlyMe) {
 | 
						|
				text = lng_chat_status_members_online(lt_count, _peer->asChannel()->membersCount(), lt_count_online, onlineCount);
 | 
						|
			} else {
 | 
						|
				text = lng_chat_status_members(lt_count, _peer->asChannel()->membersCount());
 | 
						|
			}
 | 
						|
		} else {
 | 
						|
			text = _peer->asChannel()->membersCount() ? lng_chat_status_members(lt_count, _peer->asChannel()->membersCount()) : lang(_peer->isMegagroup() ? lng_group_status : lng_channel_status);
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if (_titlePeerText != text) {
 | 
						|
		_titlePeerText = text;
 | 
						|
		_titlePeerTextOnline = titlePeerTextOnline;
 | 
						|
		_titlePeerTextWidth = st::dialogsTextFont->width(_titlePeerText);
 | 
						|
		if (App::main()) {
 | 
						|
			App::main()->topBar()->updateMembersShowArea();
 | 
						|
			App::main()->topBar()->update();
 | 
						|
		}
 | 
						|
	}
 | 
						|
	updateOnlineDisplayTimer();
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::updateOnlineDisplayTimer() {
 | 
						|
	if (!_history) return;
 | 
						|
 | 
						|
	int32 t = unixtime(), minIn = 86400;
 | 
						|
	if (_peer->isUser()) {
 | 
						|
		minIn = App::onlineWillChangeIn(_peer->asUser(), t);
 | 
						|
	} else if (_peer->isChat()) {
 | 
						|
		ChatData *chat = _peer->asChat();
 | 
						|
		if (chat->participants.isEmpty()) return;
 | 
						|
 | 
						|
		for (ChatData::Participants::const_iterator i = chat->participants.cbegin(), e = chat->participants.cend(); i != e; ++i) {
 | 
						|
			int32 onlineWillChangeIn = App::onlineWillChangeIn(i.key(), t);
 | 
						|
			if (onlineWillChangeIn < minIn) {
 | 
						|
				minIn = onlineWillChangeIn;
 | 
						|
			}
 | 
						|
		}
 | 
						|
	} else if (_peer->isChannel()) {
 | 
						|
	}
 | 
						|
	App::main()->updateOnlineDisplayIn(minIn * 1000);
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::moveFieldControls() {
 | 
						|
	int w = width(), h = height(), right = w, bottom = h, keyboardHeight = 0;
 | 
						|
	int maxKeyboardHeight = int(st::maxFieldHeight) - _field.height();
 | 
						|
	_keyboard.resizeToWidth(width(), maxKeyboardHeight);
 | 
						|
	if (_kbShown) {
 | 
						|
		keyboardHeight = qMin(_keyboard.height(), maxKeyboardHeight);
 | 
						|
		bottom -= keyboardHeight;
 | 
						|
		_kbScroll.setGeometry(0, bottom, w, keyboardHeight);
 | 
						|
	}
 | 
						|
 | 
						|
// _attachType ----------------------------------------------------------- _emojiPan --------- _fieldBarCancel
 | 
						|
// (_attachDocument|_attachPhoto) _field (_silent|_cmdStart|_kbShow) (_kbHide|_attachEmoji) [_broadcast] _send
 | 
						|
// (_botStart|_unblock|_joinChannel|_muteUnmute)
 | 
						|
 | 
						|
	int buttonsBottom = bottom - _attachDocument.height();
 | 
						|
	_attachDocument.move(0, buttonsBottom);
 | 
						|
	_attachPhoto.move(0, buttonsBottom);
 | 
						|
	_field.move(_attachDocument.width(), bottom - _field.height() - st::sendPadding);
 | 
						|
	_send.move(right - _send.width(), buttonsBottom);
 | 
						|
	if (_inlineBotCancel) _inlineBotCancel->move(_send.pos());
 | 
						|
	right -= _send.width();
 | 
						|
	_attachEmoji.move(right - _attachEmoji.width(), buttonsBottom);
 | 
						|
	_kbHide.move(right - _kbHide.width(), buttonsBottom);
 | 
						|
	right -= _attachEmoji.width();
 | 
						|
	_kbShow.move(right - _kbShow.width(), buttonsBottom);
 | 
						|
	_cmdStart.move(right - _cmdStart.width(), buttonsBottom);
 | 
						|
	_silent.move(right - _silent.width(), buttonsBottom);
 | 
						|
 | 
						|
	right = w;
 | 
						|
	_fieldBarCancel.move(right - _fieldBarCancel.width(), _field.y() - st::sendPadding - _fieldBarCancel.height());
 | 
						|
	_attachType->move(0, _attachDocument.y() - _attachType->height());
 | 
						|
	_emojiPan->moveBottom(_attachEmoji.y());
 | 
						|
 | 
						|
	_botStart.setGeometry(0, bottom - _botStart.height(), w, _botStart.height());
 | 
						|
	_unblock.setGeometry(0, bottom - _unblock.height(), w, _unblock.height());
 | 
						|
	_joinChannel.setGeometry(0, bottom - _joinChannel.height(), w, _joinChannel.height());
 | 
						|
	_muteUnmute.setGeometry(0, bottom - _muteUnmute.height(), w, _muteUnmute.height());
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::updateFieldSize() {
 | 
						|
	bool kbShowShown = _history && !_kbShown && _keyboard.hasMarkup();
 | 
						|
	int fieldWidth = width() - _attachDocument.width();
 | 
						|
	fieldWidth -= _send.width();
 | 
						|
	fieldWidth -= _attachEmoji.width();
 | 
						|
	if (kbShowShown) fieldWidth -= _kbShow.width();
 | 
						|
	if (_cmdStartShown) fieldWidth -= _cmdStart.width();
 | 
						|
	if (hasSilentToggle()) fieldWidth -= _silent.width();
 | 
						|
 | 
						|
	if (_field.width() != fieldWidth) {
 | 
						|
		_field.resize(fieldWidth, _field.height());
 | 
						|
	} else {
 | 
						|
		moveFieldControls();
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::clearInlineBot() {
 | 
						|
	if (_inlineBot) {
 | 
						|
		_inlineBot = nullptr;
 | 
						|
		inlineBotChanged();
 | 
						|
		_field.finishPlaceholder();
 | 
						|
	}
 | 
						|
	_emojiPan->clearInlineBot();
 | 
						|
	onCheckFieldAutocomplete();
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::inlineBotChanged() {
 | 
						|
	bool isInlineBot = _inlineBot && (_inlineBot != LookingUpInlineBot);
 | 
						|
	if (isInlineBot && !_inlineBotCancel) {
 | 
						|
		_inlineBotCancel = std_::make_unique<IconedButton>(this, st::inlineBotCancel);
 | 
						|
		connect(_inlineBotCancel.get(), SIGNAL(clicked()), this, SLOT(onInlineBotCancel()));
 | 
						|
		_inlineBotCancel->setGeometry(_send.geometry());
 | 
						|
		_attachEmoji.raise();
 | 
						|
		updateFieldSubmitSettings();
 | 
						|
		updateControlsVisibility();
 | 
						|
	} else if (!isInlineBot && _inlineBotCancel) {
 | 
						|
		_inlineBotCancel = nullptr;
 | 
						|
		updateFieldSubmitSettings();
 | 
						|
		updateControlsVisibility();
 | 
						|
	}
 | 
						|
	updateFieldPlaceholder();
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::onFieldResize() {
 | 
						|
	moveFieldControls();
 | 
						|
	updateListSize();
 | 
						|
	updateField();
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::onFieldFocused() {
 | 
						|
	if (_list) _list->clearSelectedItems(true);
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::onCheckFieldAutocomplete() {
 | 
						|
	if (!_history || _a_show.animating()) return;
 | 
						|
 | 
						|
	bool start = false;
 | 
						|
	bool isInlineBot = _inlineBot && (_inlineBot != LookingUpInlineBot);
 | 
						|
	QString query = isInlineBot ? 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();
 | 
						|
		if (query.at(0) == '/' && _peer->isUser() && !_peer->asUser()->botInfo) return;
 | 
						|
	}
 | 
						|
	_fieldAutocomplete->showFiltered(_peer, query, start);
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::updateFieldPlaceholder() {
 | 
						|
	if (_editMsgId) {
 | 
						|
		_field.setPlaceholder(lang(lng_edit_message_text));
 | 
						|
		_send.setText(lang(lng_settings_save));
 | 
						|
	} else {
 | 
						|
		if (_inlineBot && _inlineBot != LookingUpInlineBot) {
 | 
						|
			_field.setPlaceholder(_inlineBot->botInfo->inlinePlaceholder.mid(1), _inlineBot->username.size() + 2);
 | 
						|
		} else {
 | 
						|
			_field.setPlaceholder(lang((_history && _history->isChannel() && !_history->isMegagroup()) ? (_silent.checked() ? lng_broadcast_silent_ph : lng_broadcast_ph) : lng_message_ph));
 | 
						|
		}
 | 
						|
		_send.setText(lang(lng_send_button));
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::uploadImage(const QImage &img, PrepareMediaType type, FileLoadForceConfirmType confirm, const QString &source, bool withText) {
 | 
						|
	if (!_history) return;
 | 
						|
 | 
						|
	App::wnd()->activateWindow();
 | 
						|
	auto task = new FileLoadTask(img, type, FileLoadTo(_peer->id, _silent.checked(), replyToId()), confirm, source);
 | 
						|
	if (withText) {
 | 
						|
		_confirmWithTextId = task->fileid();
 | 
						|
	}
 | 
						|
	_fileLoader.addTask(task);
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::uploadFile(const QString &file, PrepareMediaType type, FileLoadForceConfirmType confirm, bool withText) {
 | 
						|
	if (!_history) return;
 | 
						|
 | 
						|
	App::wnd()->activateWindow();
 | 
						|
	FileLoadTask *task = new FileLoadTask(file, type, FileLoadTo(_peer->id, _silent.checked(), replyToId()), confirm);
 | 
						|
	if (withText) {
 | 
						|
		_confirmWithTextId = task->fileid();
 | 
						|
	}
 | 
						|
	_fileLoader.addTask(task);
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::uploadFiles(const QStringList &files, PrepareMediaType type) {
 | 
						|
	if (!_history || files.isEmpty()) return;
 | 
						|
 | 
						|
	if (files.size() == 1 && !QFileInfo(files.at(0)).isDir()) return uploadFile(files.at(0), type);
 | 
						|
 | 
						|
	App::wnd()->activateWindow();
 | 
						|
 | 
						|
	FileLoadTo to(_peer->id, _silent.checked(), replyToId());
 | 
						|
 | 
						|
	TasksList tasks;
 | 
						|
	tasks.reserve(files.size());
 | 
						|
	for (int32 i = 0, l = files.size(); i < l; ++i) {
 | 
						|
		tasks.push_back(TaskPtr(new FileLoadTask(files.at(i), type, to, FileLoadNeverConfirm)));
 | 
						|
	}
 | 
						|
	_fileLoader.addTasks(tasks);
 | 
						|
 | 
						|
	cancelReplyAfterMediaSend(lastForceReplyReplied());
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::uploadFileContent(const QByteArray &fileContent, PrepareMediaType type) {
 | 
						|
	if (!_history) return;
 | 
						|
 | 
						|
	App::wnd()->activateWindow();
 | 
						|
	_fileLoader.addTask(new FileLoadTask(fileContent, type, FileLoadTo(_peer->id, _silent.checked(), replyToId())));
 | 
						|
	cancelReplyAfterMediaSend(lastForceReplyReplied());
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::shareContactWithConfirm(const QString &phone, const QString &fname, const QString &lname, MsgId replyTo, bool withText) {
 | 
						|
	if (!_history) return;
 | 
						|
 | 
						|
	App::wnd()->activateWindow();
 | 
						|
	_confirmWithTextId = 0xFFFFFFFFFFFFFFFFL;
 | 
						|
	Ui::showLayer(new PhotoSendBox(phone, fname, lname, replyTo));
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::confirmSendFile(const FileLoadResultPtr &file, bool ctrlShiftEnter) {
 | 
						|
	bool lastKeyboardUsed = lastForceReplyReplied(FullMsgId(peerToChannel(file->to.peer), file->to.replyTo));
 | 
						|
	if (_confirmWithTextId && _confirmWithTextId == file->id) {
 | 
						|
		onSend(ctrlShiftEnter, file->to.replyTo);
 | 
						|
		_confirmWithTextId = 0;
 | 
						|
	}
 | 
						|
 | 
						|
	FullMsgId newId(peerToChannel(file->to.peer), clientMsgId());
 | 
						|
 | 
						|
	connect(App::uploader(), SIGNAL(photoReady(const FullMsgId&,bool,const MTPInputFile&)), this, SLOT(onPhotoUploaded(const FullMsgId&,bool,const MTPInputFile&)), Qt::UniqueConnection);
 | 
						|
	connect(App::uploader(), SIGNAL(documentReady(const FullMsgId&,bool,const MTPInputFile&)), this, SLOT(onDocumentUploaded(const FullMsgId&,bool,const MTPInputFile&)), Qt::UniqueConnection);
 | 
						|
	connect(App::uploader(), SIGNAL(thumbDocumentReady(const FullMsgId&,bool,const MTPInputFile&,const MTPInputFile&)), this, SLOT(onThumbDocumentUploaded(const FullMsgId&,bool,const MTPInputFile&, const MTPInputFile&)), Qt::UniqueConnection);
 | 
						|
	connect(App::uploader(), SIGNAL(photoProgress(const FullMsgId&)), this, SLOT(onPhotoProgress(const FullMsgId&)), Qt::UniqueConnection);
 | 
						|
	connect(App::uploader(), SIGNAL(documentProgress(const FullMsgId&)), this, SLOT(onDocumentProgress(const FullMsgId&)), Qt::UniqueConnection);
 | 
						|
	connect(App::uploader(), SIGNAL(photoFailed(const FullMsgId&)), this, SLOT(onPhotoFailed(const FullMsgId&)), Qt::UniqueConnection);
 | 
						|
	connect(App::uploader(), SIGNAL(documentFailed(const FullMsgId&)), this, SLOT(onDocumentFailed(const FullMsgId&)), Qt::UniqueConnection);
 | 
						|
 | 
						|
	App::uploader()->upload(newId, file);
 | 
						|
 | 
						|
	History *h = App::history(file->to.peer);
 | 
						|
 | 
						|
	fastShowAtEnd(h);
 | 
						|
 | 
						|
	MTPDmessage::Flags flags = newMessageFlags(h->peer) | MTPDmessage::Flag::f_media; // unread, out
 | 
						|
	if (file->to.replyTo) flags |= MTPDmessage::Flag::f_reply_to_msg_id;
 | 
						|
	bool channelPost = h->peer->isChannel() && !h->peer->isMegagroup();
 | 
						|
	bool showFromName = !channelPost || h->peer->asChannel()->addsSignature();
 | 
						|
	bool silentPost = channelPost && file->to.silent;
 | 
						|
	if (channelPost) {
 | 
						|
		flags |= MTPDmessage::Flag::f_views;
 | 
						|
		flags |= MTPDmessage::Flag::f_post;
 | 
						|
	}
 | 
						|
	if (showFromName) {
 | 
						|
		flags |= MTPDmessage::Flag::f_from_id;
 | 
						|
	}
 | 
						|
	if (silentPost) {
 | 
						|
		flags |= MTPDmessage::Flag::f_silent;
 | 
						|
	}
 | 
						|
	if (file->type == PreparePhoto) {
 | 
						|
		h->addNewMessage(MTP_message(MTP_flags(flags), MTP_int(newId.msg), MTP_int(showFromName ? MTP::authedId() : 0), peerToMTP(file->to.peer), MTPnullFwdHeader, MTPint(), MTP_int(file->to.replyTo), MTP_int(unixtime()), MTP_string(""), MTP_messageMediaPhoto(file->photo, MTP_string(file->caption)), MTPnullMarkup, MTPnullEntities, MTP_int(1), MTPint()), NewMessageUnread);
 | 
						|
	} else if (file->type == PrepareDocument) {
 | 
						|
		h->addNewMessage(MTP_message(MTP_flags(flags), MTP_int(newId.msg), MTP_int(showFromName ? MTP::authedId() : 0), peerToMTP(file->to.peer), MTPnullFwdHeader, MTPint(), MTP_int(file->to.replyTo), MTP_int(unixtime()), MTP_string(""), MTP_messageMediaDocument(file->document, MTP_string(file->caption)), MTPnullMarkup, MTPnullEntities, MTP_int(1), MTPint()), NewMessageUnread);
 | 
						|
	} else if (file->type == PrepareAudio) {
 | 
						|
		if (!h->peer->isChannel()) {
 | 
						|
			flags |= MTPDmessage::Flag::f_media_unread;
 | 
						|
		}
 | 
						|
		h->addNewMessage(MTP_message(MTP_flags(flags), MTP_int(newId.msg), MTP_int(showFromName ? MTP::authedId() : 0), peerToMTP(file->to.peer), MTPnullFwdHeader, MTPint(), MTP_int(file->to.replyTo), MTP_int(unixtime()), MTP_string(""), MTP_messageMediaDocument(file->document, MTP_string(file->caption)), MTPnullMarkup, MTPnullEntities, MTP_int(1), MTPint()), NewMessageUnread);
 | 
						|
	}
 | 
						|
 | 
						|
	if (_peer && file->to.peer == _peer->id) {
 | 
						|
		App::main()->historyToDown(_history);
 | 
						|
	}
 | 
						|
	App::main()->dialogsToUp();
 | 
						|
	peerMessagesUpdated(file->to.peer);
 | 
						|
 | 
						|
	cancelReplyAfterMediaSend(lastKeyboardUsed);
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::cancelSendFile(const FileLoadResultPtr &file) {
 | 
						|
	if (_confirmWithTextId && file->id == _confirmWithTextId) {
 | 
						|
		clearFieldText();
 | 
						|
		_confirmWithTextId = 0;
 | 
						|
	}
 | 
						|
	if (!file->originalText.isEmpty()) {
 | 
						|
		_field.textCursor().insertText(file->originalText);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::confirmShareContact(const QString &phone, const QString &fname, const QString &lname, MsgId replyTo, bool ctrlShiftEnter) {
 | 
						|
	if (!_peer) return;
 | 
						|
 | 
						|
	PeerId shareToId = _peer->id;
 | 
						|
	if (_confirmWithTextId == 0xFFFFFFFFFFFFFFFFL) {
 | 
						|
		onSend(ctrlShiftEnter, replyTo);
 | 
						|
		_confirmWithTextId = 0;
 | 
						|
	}
 | 
						|
	shareContact(shareToId, phone, fname, lname, replyTo);
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::cancelShareContact() {
 | 
						|
	if (_confirmWithTextId == 0xFFFFFFFFFFFFFFFFL) {
 | 
						|
		clearFieldText();
 | 
						|
		_confirmWithTextId = 0;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::onPhotoUploaded(const FullMsgId &newId, bool silent, const MTPInputFile &file) {
 | 
						|
	if (!MTP::authedId()) return;
 | 
						|
	HistoryItem *item = App::histItemById(newId);
 | 
						|
	if (item) {
 | 
						|
		uint64 randomId = rand_value<uint64>();
 | 
						|
		App::historyRegRandom(randomId, newId);
 | 
						|
		History *hist = item->history();
 | 
						|
		MsgId replyTo = item->replyToId();
 | 
						|
		MTPmessages_SendMedia::Flags sendFlags = 0;
 | 
						|
		if (replyTo) {
 | 
						|
			sendFlags |= MTPmessages_SendMedia::Flag::f_reply_to_msg_id;
 | 
						|
		}
 | 
						|
 | 
						|
		bool channelPost = hist->peer->isChannel() && !hist->peer->isMegagroup();
 | 
						|
		bool silentPost = channelPost && silent;
 | 
						|
		if (silentPost) {
 | 
						|
			sendFlags |= MTPmessages_SendMedia::Flag::f_silent;
 | 
						|
		}
 | 
						|
		auto caption = item->getMedia() ? item->getMedia()->getCaption() : TextWithEntities();
 | 
						|
		MTPDinputMediaUploadedPhoto::Flags mediaFlags = 0;
 | 
						|
		auto media = MTP_inputMediaUploadedPhoto(MTP_flags(mediaFlags), file, MTP_string(caption.text), MTPVector<MTPInputDocument>());
 | 
						|
		hist->sendRequestId = MTP::send(MTPmessages_SendMedia(MTP_flags(sendFlags), item->history()->peer->input, MTP_int(replyTo), media, MTP_long(randomId), MTPnullMarkup), App::main()->rpcDone(&MainWidget::sentUpdatesReceived), App::main()->rpcFail(&MainWidget::sendMessageFail), 0, 0, hist->sendRequestId);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
namespace {
 | 
						|
	MTPVector<MTPDocumentAttribute> _composeDocumentAttributes(DocumentData *document) {
 | 
						|
		QVector<MTPDocumentAttribute> attributes(1, MTP_documentAttributeFilename(MTP_string(document->name)));
 | 
						|
		if (document->dimensions.width() > 0 && document->dimensions.height() > 0) {
 | 
						|
			int32 duration = document->duration();
 | 
						|
			if (duration >= 0) {
 | 
						|
				attributes.push_back(MTP_documentAttributeVideo(MTP_int(duration), MTP_int(document->dimensions.width()), MTP_int(document->dimensions.height())));
 | 
						|
			} else {
 | 
						|
				attributes.push_back(MTP_documentAttributeImageSize(MTP_int(document->dimensions.width()), MTP_int(document->dimensions.height())));
 | 
						|
			}
 | 
						|
		}
 | 
						|
		if (document->type == AnimatedDocument) {
 | 
						|
			attributes.push_back(MTP_documentAttributeAnimated());
 | 
						|
		} else if (document->type == StickerDocument && document->sticker()) {
 | 
						|
			MTPDdocumentAttributeSticker::Flags stickerFlags = 0;
 | 
						|
			attributes.push_back(MTP_documentAttributeSticker(MTP_flags(stickerFlags), MTP_string(document->sticker()->alt), document->sticker()->set, MTPMaskCoords()));
 | 
						|
		} else if (document->type == SongDocument && document->song()) {
 | 
						|
			attributes.push_back(MTP_documentAttributeAudio(MTP_flags(MTPDdocumentAttributeAudio::Flag::f_title | MTPDdocumentAttributeAudio::Flag::f_performer), MTP_int(document->song()->duration), MTP_string(document->song()->title), MTP_string(document->song()->performer), MTPstring()));
 | 
						|
		} else if (document->type == VoiceDocument && document->voice()) {
 | 
						|
			attributes.push_back(MTP_documentAttributeAudio(MTP_flags(MTPDdocumentAttributeAudio::Flag::f_voice | MTPDdocumentAttributeAudio::Flag::f_waveform), MTP_int(document->voice()->duration), MTPstring(), MTPstring(), MTP_bytes(documentWaveformEncode5bit(document->voice()->waveform))));
 | 
						|
		}
 | 
						|
		return MTP_vector<MTPDocumentAttribute>(attributes);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::onDocumentUploaded(const FullMsgId &newId, bool silent, const MTPInputFile &file) {
 | 
						|
	if (!MTP::authedId()) return;
 | 
						|
	HistoryMessage *item = dynamic_cast<HistoryMessage*>(App::histItemById(newId));
 | 
						|
	if (item) {
 | 
						|
		DocumentData *document = item->getMedia() ? item->getMedia()->getDocument() : 0;
 | 
						|
		if (document) {
 | 
						|
			uint64 randomId = rand_value<uint64>();
 | 
						|
			App::historyRegRandom(randomId, newId);
 | 
						|
			History *hist = item->history();
 | 
						|
			MsgId replyTo = item->replyToId();
 | 
						|
			MTPmessages_SendMedia::Flags sendFlags = 0;
 | 
						|
			if (replyTo) {
 | 
						|
				sendFlags |= MTPmessages_SendMedia::Flag::f_reply_to_msg_id;
 | 
						|
			}
 | 
						|
 | 
						|
			bool channelPost = hist->peer->isChannel() && !hist->peer->isMegagroup();
 | 
						|
			bool silentPost = channelPost && silent;
 | 
						|
			if (silentPost) {
 | 
						|
				sendFlags |= MTPmessages_SendMedia::Flag::f_silent;
 | 
						|
			}
 | 
						|
			auto caption = item->getMedia() ? item->getMedia()->getCaption() : TextWithEntities();
 | 
						|
			MTPDinputMediaUploadedDocument::Flags mediaFlags = 0;
 | 
						|
			auto media = MTP_inputMediaUploadedDocument(MTP_flags(mediaFlags), file, MTP_string(document->mime), _composeDocumentAttributes(document), MTP_string(caption.text), MTPVector<MTPInputDocument>());
 | 
						|
			hist->sendRequestId = MTP::send(MTPmessages_SendMedia(MTP_flags(sendFlags), item->history()->peer->input, MTP_int(replyTo), media, MTP_long(randomId), MTPnullMarkup), App::main()->rpcDone(&MainWidget::sentUpdatesReceived), App::main()->rpcFail(&MainWidget::sendMessageFail), 0, 0, hist->sendRequestId);
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::onThumbDocumentUploaded(const FullMsgId &newId, bool silent, const MTPInputFile &file, const MTPInputFile &thumb) {
 | 
						|
	if (!MTP::authedId()) return;
 | 
						|
	HistoryMessage *item = dynamic_cast<HistoryMessage*>(App::histItemById(newId));
 | 
						|
	if (item) {
 | 
						|
		DocumentData *document = item->getMedia() ? item->getMedia()->getDocument() : 0;
 | 
						|
		if (document) {
 | 
						|
			uint64 randomId = rand_value<uint64>();
 | 
						|
			App::historyRegRandom(randomId, newId);
 | 
						|
			History *hist = item->history();
 | 
						|
			MsgId replyTo = item->replyToId();
 | 
						|
			MTPmessages_SendMedia::Flags sendFlags = 0;
 | 
						|
			if (replyTo) {
 | 
						|
				sendFlags |= MTPmessages_SendMedia::Flag::f_reply_to_msg_id;
 | 
						|
			}
 | 
						|
 | 
						|
			bool channelPost = hist->peer->isChannel() && !hist->peer->isMegagroup();
 | 
						|
			bool silentPost = channelPost && silent;
 | 
						|
			if (silentPost) {
 | 
						|
				sendFlags |= MTPmessages_SendMedia::Flag::f_silent;
 | 
						|
			}
 | 
						|
			auto caption = item->getMedia() ? item->getMedia()->getCaption() : TextWithEntities();
 | 
						|
			MTPDinputMediaUploadedThumbDocument::Flags mediaFlags = 0;
 | 
						|
			auto media = MTP_inputMediaUploadedThumbDocument(MTP_flags(mediaFlags), file, thumb, MTP_string(document->mime), _composeDocumentAttributes(document), MTP_string(caption.text), MTPVector<MTPInputDocument>());
 | 
						|
			hist->sendRequestId = MTP::send(MTPmessages_SendMedia(MTP_flags(sendFlags), item->history()->peer->input, MTP_int(replyTo), media, MTP_long(randomId), MTPnullMarkup), App::main()->rpcDone(&MainWidget::sentUpdatesReceived), App::main()->rpcFail(&MainWidget::sendMessageFail), 0, 0, hist->sendRequestId);
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::onPhotoProgress(const FullMsgId &newId) {
 | 
						|
	if (!MTP::authedId()) return;
 | 
						|
	if (HistoryItem *item = App::histItemById(newId)) {
 | 
						|
		PhotoData *photo = (item->getMedia() && item->getMedia()->type() == MediaTypePhoto) ? static_cast<HistoryPhoto*>(item->getMedia())->photo() : 0;
 | 
						|
		if (!item->isPost()) {
 | 
						|
			updateSendAction(item->history(), SendActionUploadPhoto, 0);
 | 
						|
		}
 | 
						|
		Ui::repaintHistoryItem(item);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::onDocumentProgress(const FullMsgId &newId) {
 | 
						|
	if (!MTP::authedId()) return;
 | 
						|
	if (HistoryItem *item = App::histItemById(newId)) {
 | 
						|
		HistoryMedia *media = item->getMedia();
 | 
						|
		DocumentData *doc = media ? media->getDocument() : 0;
 | 
						|
		if (!item->isPost()) {
 | 
						|
			updateSendAction(item->history(), (doc && doc->voice()) ? SendActionUploadVoice : SendActionUploadFile, doc ? doc->uploadOffset : 0);
 | 
						|
		}
 | 
						|
		Ui::repaintHistoryItem(item);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::onPhotoFailed(const FullMsgId &newId) {
 | 
						|
	if (!MTP::authedId()) return;
 | 
						|
	HistoryItem *item = App::histItemById(newId);
 | 
						|
	if (item) {
 | 
						|
		if (!item->isPost()) {
 | 
						|
			updateSendAction(item->history(), SendActionUploadPhoto, -1);
 | 
						|
		}
 | 
						|
//		Ui::repaintHistoryItem(item);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::onDocumentFailed(const FullMsgId &newId) {
 | 
						|
	if (!MTP::authedId()) return;
 | 
						|
	HistoryItem *item = App::histItemById(newId);
 | 
						|
	if (item) {
 | 
						|
		HistoryMedia *media = item->getMedia();
 | 
						|
		DocumentData *doc = media ? media->getDocument() : 0;
 | 
						|
		if (!item->isPost()) {
 | 
						|
			updateSendAction(item->history(), (doc && doc->voice()) ? SendActionUploadVoice : SendActionUploadFile, -1);
 | 
						|
		}
 | 
						|
		Ui::repaintHistoryItem(item);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::onReportSpamClicked() {
 | 
						|
	ConfirmBox *box = new ConfirmBox(lang(_peer->isUser() ? lng_report_spam_sure : ((_peer->isChat() || _peer->isMegagroup()) ? lng_report_spam_sure_group : lng_report_spam_sure_channel)), lang(lng_report_spam_ok), st::attentionBoxButton);
 | 
						|
	connect(box, SIGNAL(confirmed()), this, SLOT(onReportSpamSure()));
 | 
						|
	Ui::showLayer(box);
 | 
						|
	_clearPeer = _peer;
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::onReportSpamSure() {
 | 
						|
	if (_reportSpamRequest) return;
 | 
						|
 | 
						|
	Ui::hideLayer();
 | 
						|
	if (_clearPeer->isUser()) MTP::send(MTPcontacts_Block(_clearPeer->asUser()->inputUser), rpcDone(&HistoryWidget::blockDone, _clearPeer), RPCFailHandlerPtr(), 0, 5);
 | 
						|
	_reportSpamRequest = MTP::send(MTPmessages_ReportSpam(_clearPeer->input), rpcDone(&HistoryWidget::reportSpamDone, _clearPeer), rpcFail(&HistoryWidget::reportSpamFail));
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::reportSpamDone(PeerData *peer, const MTPBool &result, mtpRequestId req) {
 | 
						|
	if (req == _reportSpamRequest) {
 | 
						|
		_reportSpamRequest = 0;
 | 
						|
	}
 | 
						|
	if (peer) {
 | 
						|
		cRefReportSpamStatuses().insert(peer->id, dbiprsReportSent);
 | 
						|
		Local::writeReportSpamStatuses();
 | 
						|
	}
 | 
						|
	_reportSpamStatus = dbiprsReportSent;
 | 
						|
	_reportSpamPanel.setReported(_reportSpamStatus == dbiprsReportSent, peer);
 | 
						|
}
 | 
						|
 | 
						|
bool HistoryWidget::reportSpamFail(const RPCError &error, mtpRequestId req) {
 | 
						|
	if (MTP::isDefaultHandledError(error)) return false;
 | 
						|
 | 
						|
	if (req == _reportSpamRequest) {
 | 
						|
		_reportSpamRequest = 0;
 | 
						|
	}
 | 
						|
	return false;
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::onReportSpamHide() {
 | 
						|
	if (_peer) {
 | 
						|
		cRefReportSpamStatuses().insert(_peer->id, dbiprsHidden);
 | 
						|
		Local::writeReportSpamStatuses();
 | 
						|
 | 
						|
		MTP::send(MTPmessages_HideReportSpam(_peer->input));
 | 
						|
	}
 | 
						|
	_reportSpamStatus = dbiprsHidden;
 | 
						|
	updateControlsVisibility();
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::onReportSpamClear() {
 | 
						|
	_clearPeer = _peer;
 | 
						|
	if (_clearPeer->isUser()) {
 | 
						|
		App::main()->deleteConversation(_clearPeer);
 | 
						|
	} else if (_clearPeer->isChat()) {
 | 
						|
		App::main()->showBackFromStack();
 | 
						|
		MTP::send(MTPmessages_DeleteChatUser(_clearPeer->asChat()->inputChat, App::self()->inputUser), App::main()->rpcDone(&MainWidget::deleteHistoryAfterLeave, _clearPeer), App::main()->rpcFail(&MainWidget::leaveChatFailed, _clearPeer));
 | 
						|
	} else if (_clearPeer->isChannel()) {
 | 
						|
		App::main()->showBackFromStack();
 | 
						|
		if (_clearPeer->migrateFrom()) {
 | 
						|
			App::main()->deleteConversation(_clearPeer->migrateFrom());
 | 
						|
		}
 | 
						|
		MTP::send(MTPchannels_LeaveChannel(_clearPeer->asChannel()->inputChannel), App::main()->rpcDone(&MainWidget::sentUpdatesReceived));
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::peerMessagesUpdated(PeerId peer) {
 | 
						|
	if (_peer && _list && peer == _peer->id) {
 | 
						|
		updateListSize();
 | 
						|
		updateBotKeyboard();
 | 
						|
		if (!_scroll.isHidden()) {
 | 
						|
			bool unblock = isBlocked(), botStart = isBotStart(), joinChannel = isJoinChannel(), muteUnmute = isMuteUnmute();
 | 
						|
			bool upd = (_unblock.isHidden() == unblock);
 | 
						|
			if (!upd && !unblock) upd = (_botStart.isHidden() == botStart);
 | 
						|
			if (!upd && !unblock && !botStart) upd = (_joinChannel.isHidden() == joinChannel);
 | 
						|
			if (!upd && !unblock && !botStart && !joinChannel) upd = (_muteUnmute.isHidden() == muteUnmute);
 | 
						|
			if (upd) {
 | 
						|
				updateControlsVisibility();
 | 
						|
				updateControlsGeometry();
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::peerMessagesUpdated() {
 | 
						|
	if (_list) peerMessagesUpdated(_peer->id);
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::grapWithoutTopBarShadow() {
 | 
						|
	grabStart();
 | 
						|
	_topShadow->hide();
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::grabFinish() {
 | 
						|
	_inGrab = false;
 | 
						|
	resizeEvent(0);
 | 
						|
	_topShadow->show();
 | 
						|
}
 | 
						|
 | 
						|
bool HistoryWidget::isItemVisible(HistoryItem *item) {
 | 
						|
	if (isHidden() || _a_show.animating() || !_list) {
 | 
						|
		return false;
 | 
						|
	}
 | 
						|
	int32 top = _list->itemTop(item), st = _scroll.scrollTop();
 | 
						|
	if (top < 0 || top + item->height() <= st || top >= st + _scroll.height()) {
 | 
						|
		return false;
 | 
						|
	}
 | 
						|
	return true;
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::ui_repaintHistoryItem(const HistoryItem *item) {
 | 
						|
	if (_peer && _list && (item->history() == _history || (_migrated && item->history() == _migrated))) {
 | 
						|
		uint64 ms = getms();
 | 
						|
		if (_lastScrolled + 100 <= ms) {
 | 
						|
			_list->repaintItem(item);
 | 
						|
		} else {
 | 
						|
			_updateHistoryItems.start(_lastScrolled + 100 - ms);
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::onUpdateHistoryItems() {
 | 
						|
	if (!_list) return;
 | 
						|
 | 
						|
	uint64 ms = getms();
 | 
						|
	if (_lastScrolled + 100 <= ms) {
 | 
						|
		_list->update();
 | 
						|
	} else {
 | 
						|
		_updateHistoryItems.start(_lastScrolled + 100 - ms);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::ui_repaintInlineItem(const InlineBots::Layout::ItemBase *layout) {
 | 
						|
	_emojiPan->ui_repaintInlineItem(layout);
 | 
						|
}
 | 
						|
 | 
						|
bool HistoryWidget::ui_isInlineItemVisible(const InlineBots::Layout::ItemBase *layout) {
 | 
						|
	return _emojiPan->ui_isInlineItemVisible(layout);
 | 
						|
}
 | 
						|
 | 
						|
bool HistoryWidget::ui_isInlineItemBeingChosen() {
 | 
						|
	return _emojiPan->ui_isInlineItemBeingChosen();
 | 
						|
}
 | 
						|
 | 
						|
PeerData *HistoryWidget::ui_getPeerForMouseAction() {
 | 
						|
	return _peer;
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::notify_historyItemLayoutChanged(const HistoryItem *item) {
 | 
						|
	if (_peer && _list && (item == App::mousedItem() || item == App::hoveredItem() || item == App::hoveredLinkItem())) {
 | 
						|
		_list->onUpdateSelected();
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::notify_inlineItemLayoutChanged(const InlineBots::Layout::ItemBase *layout) {
 | 
						|
	_emojiPan->notify_inlineItemLayoutChanged(layout);
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::notify_handlePendingHistoryUpdate() {
 | 
						|
	if (hasPendingResizedItems()) {
 | 
						|
		updateListSize();
 | 
						|
		_list->update();
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::resizeEvent(QResizeEvent *e) {
 | 
						|
	updateControlsGeometry();
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::updateControlsGeometry() {
 | 
						|
	_reportSpamPanel.resize(width(), _reportSpamPanel.height());
 | 
						|
 | 
						|
	moveFieldControls();
 | 
						|
 | 
						|
	if (_pinnedBar) {
 | 
						|
		if (_scroll.y() != st::replyHeight) {
 | 
						|
			_scroll.move(0, st::replyHeight);
 | 
						|
			_reportSpamPanel.move(0, st::replyHeight);
 | 
						|
			_fieldAutocomplete->setBoundings(_scroll.geometry());
 | 
						|
		}
 | 
						|
		_pinnedBar->cancel->move(width() - _pinnedBar->cancel->width(), 0);
 | 
						|
		_pinnedBar->shadow->setGeometry(0, st::replyHeight, width(), st::lineWidth);
 | 
						|
	} else if (_scroll.y() != 0) {
 | 
						|
		_scroll.move(0, 0);
 | 
						|
		_reportSpamPanel.move(0, 0);
 | 
						|
		_fieldAutocomplete->setBoundings(_scroll.geometry());
 | 
						|
	}
 | 
						|
 | 
						|
	updateListSize(false, false, { ScrollChangeAdd, App::main() ? App::main()->contentScrollAddToY() : 0 });
 | 
						|
 | 
						|
	updateFieldSize();
 | 
						|
 | 
						|
	_historyToEnd->moveToRight(st::historyToDownPosition.x(), _scroll.y() + _scroll.height() - _historyToEnd->height() - st::historyToDownPosition.y());
 | 
						|
 | 
						|
	_emojiPan->setMaxHeight(height() - st::dropdownDef.padding.top() - st::dropdownDef.padding.bottom() - _attachEmoji.height());
 | 
						|
	if (_membersDropdown) {
 | 
						|
		_membersDropdown->setMaxHeight(countMembersDropdownHeightMax());
 | 
						|
	}
 | 
						|
 | 
						|
	switch (_attachDrag) {
 | 
						|
	case DragStateFiles:
 | 
						|
		_attachDragDocument->resize(width() - st::dragMargin.left() - st::dragMargin.right(), height() - st::dragMargin.top() - st::dragMargin.bottom());
 | 
						|
		_attachDragDocument->move(st::dragMargin.left(), st::dragMargin.top());
 | 
						|
	break;
 | 
						|
	case DragStatePhotoFiles:
 | 
						|
		_attachDragDocument->resize(width() - st::dragMargin.left() - st::dragMargin.right(), (height() - st::dragMargin.top() - st::dragMargin.bottom()) / 2);
 | 
						|
		_attachDragDocument->move(st::dragMargin.left(), st::dragMargin.top());
 | 
						|
		_attachDragPhoto->resize(_attachDragDocument->width(), _attachDragDocument->height());
 | 
						|
		_attachDragPhoto->move(st::dragMargin.left(), height() - _attachDragPhoto->height() - st::dragMargin.bottom());
 | 
						|
	break;
 | 
						|
	case DragStateImage:
 | 
						|
		_attachDragPhoto->resize(width() - st::dragMargin.left() - st::dragMargin.right(), height() - st::dragMargin.top() - st::dragMargin.bottom());
 | 
						|
		_attachDragPhoto->move(st::dragMargin.left(), st::dragMargin.top());
 | 
						|
	break;
 | 
						|
	}
 | 
						|
 | 
						|
	_topShadow->resize(width() - ((!Adaptive::OneColumn() && !_inGrab) ? st::lineWidth : 0), st::lineWidth);
 | 
						|
	_topShadow->moveToLeft((!Adaptive::OneColumn() && !_inGrab) ? st::lineWidth : 0, 0);
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::itemRemoved(HistoryItem *item) {
 | 
						|
	if (item == _replyEditMsg) {
 | 
						|
		if (_editMsgId) {
 | 
						|
			cancelEdit();
 | 
						|
		} else {
 | 
						|
			cancelReply();
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if (item == _replyReturn) {
 | 
						|
		calcNextReplyReturn();
 | 
						|
	}
 | 
						|
	if (_pinnedBar && item->id == _pinnedBar->msgId) {
 | 
						|
		pinnedMsgVisibilityUpdated();
 | 
						|
	}
 | 
						|
	if (_kbReplyTo && item == _kbReplyTo) {
 | 
						|
		onKbToggle();
 | 
						|
		_kbReplyTo = 0;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::itemEdited(HistoryItem *item) {
 | 
						|
	if (item == _replyEditMsg) {
 | 
						|
		updateReplyEditTexts(true);
 | 
						|
	}
 | 
						|
	if (_pinnedBar && item->id == _pinnedBar->msgId) {
 | 
						|
		updatePinnedBar(true);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::updateScrollColors() {
 | 
						|
	if (!App::historyScrollBarColor()) return;
 | 
						|
	_scroll.updateColors(App::historyScrollBarColor(), App::historyScrollBgColor(), App::historyScrollBarOverColor(), App::historyScrollBgOverColor());
 | 
						|
}
 | 
						|
 | 
						|
MsgId HistoryWidget::replyToId() const {
 | 
						|
	return _replyToId ? _replyToId : (_kbReplyTo ? _kbReplyTo->id : 0);
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::updateListSize(bool initial, bool loadedDown, const ScrollChange &change) {
 | 
						|
	if (!_history || (initial && _histInited) || (!initial && !_histInited)) return;
 | 
						|
	if (_firstLoadRequest || _a_show.animating()) {
 | 
						|
		return; // scrollTopMax etc are not working after recountHeight()
 | 
						|
	}
 | 
						|
 | 
						|
	int newScrollHeight = height();
 | 
						|
	if (isBlocked() || isBotStart() || isJoinChannel() || isMuteUnmute()) {
 | 
						|
		newScrollHeight -= _unblock.height();
 | 
						|
	} else {
 | 
						|
		if (_canSendMessages) {
 | 
						|
			newScrollHeight -= (_field.height() + 2 * st::sendPadding);
 | 
						|
		}
 | 
						|
		if (_editMsgId || replyToId() || readyToForward() || (_previewData && _previewData->pendingTill >= 0)) {
 | 
						|
			newScrollHeight -= st::replyHeight;
 | 
						|
		}
 | 
						|
		if (_kbShown) {
 | 
						|
			newScrollHeight -= _kbScroll.height();
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if (_pinnedBar) {
 | 
						|
		newScrollHeight -= st::replyHeight;
 | 
						|
	}
 | 
						|
	int wasScrollTop = _scroll.scrollTop();
 | 
						|
	bool wasAtBottom = wasScrollTop + 1 > _scroll.scrollTopMax();
 | 
						|
	bool needResize = (_scroll.width() != width()) || (_scroll.height() != newScrollHeight);
 | 
						|
	if (needResize) {
 | 
						|
		_scroll.resize(width(), newScrollHeight);
 | 
						|
		// on initial updateListSize we didn't put the _scroll.scrollTop correctly yet
 | 
						|
		// so visibleAreaUpdated() call will erase it with the new (undefined) value
 | 
						|
		if (!initial) {
 | 
						|
			visibleAreaUpdated();
 | 
						|
		}
 | 
						|
 | 
						|
		_fieldAutocomplete->setBoundings(_scroll.geometry());
 | 
						|
		_historyToEnd->moveToRight(st::historyToDownPosition.x(), _scroll.y() + _scroll.height() - _historyToEnd->height() - st::historyToDownPosition.y());
 | 
						|
	}
 | 
						|
 | 
						|
	_list->recountHeight();
 | 
						|
 | 
						|
	bool washidden = _scroll.isHidden();
 | 
						|
	if (washidden) {
 | 
						|
		_scroll.show();
 | 
						|
	}
 | 
						|
	_list->updateSize();
 | 
						|
	if (washidden) {
 | 
						|
		_scroll.hide();
 | 
						|
	}
 | 
						|
 | 
						|
	if ((!initial && !wasAtBottom) || (loadedDown && (!_history->showFrom || _history->unreadBar || _history->loadedAtBottom()) && (!_migrated || !_migrated->showFrom || _migrated->unreadBar || _history->loadedAtBottom()))) {
 | 
						|
		int toY = _list->historyScrollTop();
 | 
						|
		if (change.type == ScrollChangeAdd) {
 | 
						|
			toY += change.value;
 | 
						|
		} else if (change.type == ScrollChangeNoJumpToBottom) {
 | 
						|
			toY = wasScrollTop;
 | 
						|
		} else if (_addToScroll) {
 | 
						|
			toY += _addToScroll;
 | 
						|
			_addToScroll = 0;
 | 
						|
		}
 | 
						|
		if (toY > _scroll.scrollTopMax()) {
 | 
						|
			toY = _scroll.scrollTopMax();
 | 
						|
		}
 | 
						|
		if (_scroll.scrollTop() == toY) {
 | 
						|
			visibleAreaUpdated();
 | 
						|
		} else {
 | 
						|
			_scroll.scrollToY(toY);
 | 
						|
		}
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	if (initial) {
 | 
						|
		_histInited = true;
 | 
						|
	}
 | 
						|
 | 
						|
	int32 toY = ScrollMax;
 | 
						|
	if (initial && (_history->scrollTopItem || (_migrated && _migrated->scrollTopItem))) {
 | 
						|
		toY = _list->historyScrollTop();
 | 
						|
	} else if (initial && _migrated && _showAtMsgId < 0 && -_showAtMsgId < ServerMaxMsgId) {
 | 
						|
		HistoryItem *item = App::histItemById(0, -_showAtMsgId);
 | 
						|
		int32 iy = _list->itemTop(item);
 | 
						|
		if (iy < 0) {
 | 
						|
			setMsgId(0);
 | 
						|
			_histInited = false;
 | 
						|
			return updateListSize(initial, false, change);
 | 
						|
		} else {
 | 
						|
			toY = (_scroll.height() > item->height()) ? qMax(iy - (_scroll.height() - item->height()) / 2, 0) : iy;
 | 
						|
			_animActiveStart = getms();
 | 
						|
			_animActiveTimer.start(AnimationTimerDelta);
 | 
						|
			_activeAnimMsgId = _showAtMsgId;
 | 
						|
		}
 | 
						|
	} else if (initial && _showAtMsgId > 0) {
 | 
						|
		HistoryItem *item = App::histItemById(_channel, _showAtMsgId);
 | 
						|
		int32 iy = _list->itemTop(item);
 | 
						|
		if (iy < 0) {
 | 
						|
			setMsgId(0);
 | 
						|
			_histInited = false;
 | 
						|
			return updateListSize(initial, false, change);
 | 
						|
		} else {
 | 
						|
			toY = (_scroll.height() > item->height()) ? qMax(iy - (_scroll.height() - item->height()) / 2, 0) : iy;
 | 
						|
			_animActiveStart = getms();
 | 
						|
			_animActiveTimer.start(AnimationTimerDelta);
 | 
						|
			_activeAnimMsgId = _showAtMsgId;
 | 
						|
			if (item->isGroupMigrate() && _migrated && !_migrated->isEmpty() && _migrated->loadedAtBottom() && _migrated->blocks.back()->items.back()->isGroupMigrate() && _list->historyTop() != _list->historyDrawTop()) {
 | 
						|
				_activeAnimMsgId = -_migrated->blocks.back()->items.back()->id;
 | 
						|
			}
 | 
						|
		}
 | 
						|
	} else if (initial && (_history->unreadBar || (_migrated && _migrated->unreadBar))) {
 | 
						|
		toY = unreadBarTop();
 | 
						|
	} else if (_migrated && _migrated->showFrom) {
 | 
						|
		toY = _list->itemTop(_migrated->showFrom);
 | 
						|
		if (toY < _scroll.scrollTopMax() + HistoryMessageUnreadBar::height() - HistoryMessageUnreadBar::marginTop()) {
 | 
						|
			_migrated->addUnreadBar();
 | 
						|
			if (_migrated->unreadBar) {
 | 
						|
				setMsgId(ShowAtUnreadMsgId);
 | 
						|
				_histInited = false;
 | 
						|
				updateListSize(true);
 | 
						|
				App::wnd()->checkHistoryActivation();
 | 
						|
				return;
 | 
						|
			}
 | 
						|
		}
 | 
						|
	} else if (_history->showFrom) {
 | 
						|
		toY = _list->itemTop(_history->showFrom);
 | 
						|
		if (toY < _scroll.scrollTopMax() + st::unreadBarHeight) {
 | 
						|
			_history->addUnreadBar();
 | 
						|
			if (_history->unreadBar) {
 | 
						|
				setMsgId(ShowAtUnreadMsgId);
 | 
						|
				_histInited = false;
 | 
						|
				updateListSize(true);
 | 
						|
				App::wnd()->checkHistoryActivation();
 | 
						|
				return;
 | 
						|
			}
 | 
						|
		}
 | 
						|
	} else {
 | 
						|
	}
 | 
						|
	auto scrollMax = _scroll.scrollTopMax();
 | 
						|
	accumulate_min(toY, scrollMax);
 | 
						|
	if (_scroll.scrollTop() == toY) {
 | 
						|
		visibleAreaUpdated();
 | 
						|
	} else {
 | 
						|
		_scroll.scrollToY(toY);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
int HistoryWidget::unreadBarTop() const {
 | 
						|
	auto getUnreadBar = [this]() -> HistoryItem* {
 | 
						|
		if (_migrated && _migrated->unreadBar) {
 | 
						|
			return _migrated->unreadBar;
 | 
						|
		}
 | 
						|
		if (_history->unreadBar) {
 | 
						|
			return _history->unreadBar;
 | 
						|
		}
 | 
						|
		return nullptr;
 | 
						|
	};
 | 
						|
	if (HistoryItem *bar = getUnreadBar()) {
 | 
						|
		int result = _list->itemTop(bar) + HistoryMessageUnreadBar::marginTop();
 | 
						|
		if (bar->Has<HistoryMessageDate>()) {
 | 
						|
			result += bar->Get<HistoryMessageDate>()->height();
 | 
						|
		}
 | 
						|
		return result;
 | 
						|
	}
 | 
						|
	return -1;
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::addMessagesToFront(PeerData *peer, const QVector<MTPMessage> &messages) {
 | 
						|
	_list->messagesReceived(peer, messages);
 | 
						|
	if (!_firstLoadRequest) {
 | 
						|
		updateListSize();
 | 
						|
		if (_animActiveTimer.isActive() && _activeAnimMsgId > 0 && _migrated && !_migrated->isEmpty() && _migrated->loadedAtBottom() && _migrated->blocks.back()->items.back()->isGroupMigrate() && _list->historyTop() != _list->historyDrawTop() && _history) {
 | 
						|
			HistoryItem *animActiveItem = App::histItemById(_history->channelId(), _activeAnimMsgId);
 | 
						|
			if (animActiveItem && animActiveItem->isGroupMigrate()) {
 | 
						|
				_activeAnimMsgId = -_migrated->blocks.back()->items.back()->id;
 | 
						|
			}
 | 
						|
		}
 | 
						|
		updateBotKeyboard();
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::addMessagesToBack(PeerData *peer, const QVector<MTPMessage> &messages) {
 | 
						|
	_list->messagesReceivedDown(peer, messages);
 | 
						|
	if (!_firstLoadRequest) {
 | 
						|
		updateListSize(false, true, { ScrollChangeNoJumpToBottom, 0 });
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::countHistoryShowFrom() {
 | 
						|
	if (_migrated && _showAtMsgId == ShowAtUnreadMsgId && _migrated->unreadCount()) {
 | 
						|
		_migrated->updateShowFrom();
 | 
						|
	}
 | 
						|
	if ((_migrated && _migrated->showFrom) || _showAtMsgId != ShowAtUnreadMsgId || !_history->unreadCount()) {
 | 
						|
		_history->showFrom = nullptr;
 | 
						|
		return;
 | 
						|
	}
 | 
						|
	_history->updateShowFrom();
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::updateBotKeyboard(History *h, bool force) {
 | 
						|
	if (h && h != _history && h != _migrated) {
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	bool changed = false;
 | 
						|
	bool wasVisible = _kbShown || _kbReplyTo;
 | 
						|
	if ((_replyToId && !_replyEditMsg) || _editMsgId || !_history) {
 | 
						|
		changed = _keyboard.updateMarkup(nullptr, force);
 | 
						|
	} else if (_replyToId && _replyEditMsg) {
 | 
						|
		changed = _keyboard.updateMarkup(_replyEditMsg, force);
 | 
						|
	} else {
 | 
						|
		HistoryItem *keyboardItem = _history->lastKeyboardId ? App::histItemById(_channel, _history->lastKeyboardId) : nullptr;
 | 
						|
		changed = _keyboard.updateMarkup(keyboardItem, force);
 | 
						|
	}
 | 
						|
	updateCmdStartShown();
 | 
						|
	if (!changed) return;
 | 
						|
 | 
						|
	bool hasMarkup = _keyboard.hasMarkup(), forceReply = _keyboard.forceReply() && (!_replyToId || !_replyEditMsg);
 | 
						|
	if (hasMarkup || forceReply) {
 | 
						|
		if (_keyboard.singleUse() && _keyboard.hasMarkup() && _keyboard.forMsgId() == FullMsgId(_channel, _history->lastKeyboardId) && _history->lastKeyboardUsed) {
 | 
						|
			_history->lastKeyboardHiddenId = _history->lastKeyboardId;
 | 
						|
		}
 | 
						|
		if (!isBotStart() && !isBlocked() && _canSendMessages && (wasVisible || (_replyToId && _replyEditMsg) || (!_field.hasSendText() && !kbWasHidden()))) {
 | 
						|
			if (!_a_show.animating()) {
 | 
						|
				if (hasMarkup) {
 | 
						|
					_kbScroll.show();
 | 
						|
					_attachEmoji.hide();
 | 
						|
					_kbHide.show();
 | 
						|
				} else {
 | 
						|
					_kbScroll.hide();
 | 
						|
					_attachEmoji.show();
 | 
						|
					_kbHide.hide();
 | 
						|
				}
 | 
						|
				_kbShow.hide();
 | 
						|
				_cmdStart.hide();
 | 
						|
			}
 | 
						|
			int32 maxh = hasMarkup ? qMin(_keyboard.height(), int(st::maxFieldHeight) - (int(st::maxFieldHeight) / 2)) : 0;
 | 
						|
			_field.setMaxHeight(st::maxFieldHeight - maxh);
 | 
						|
			_kbShown = hasMarkup;
 | 
						|
			_kbReplyTo = (_peer->isChat() || _peer->isChannel() || _keyboard.forceReply()) ? App::histItemById(_keyboard.forMsgId()) : 0;
 | 
						|
			if (_kbReplyTo && !_replyToId) {
 | 
						|
				updateReplyToName();
 | 
						|
				_replyEditMsgText.setText(st::msgFont, textClean(_kbReplyTo->inReplyText()), _textDlgOptions);
 | 
						|
				_fieldBarCancel.show();
 | 
						|
				updateMouseTracking();
 | 
						|
			}
 | 
						|
		} else {
 | 
						|
			if (!_a_show.animating()) {
 | 
						|
				_kbScroll.hide();
 | 
						|
				_attachEmoji.show();
 | 
						|
				_kbHide.hide();
 | 
						|
				_kbShow.show();
 | 
						|
				_cmdStart.hide();
 | 
						|
			}
 | 
						|
			_field.setMaxHeight(st::maxFieldHeight);
 | 
						|
			_kbShown = false;
 | 
						|
			_kbReplyTo = 0;
 | 
						|
			if (!readyToForward() && (!_previewData || _previewData->pendingTill < 0) && !_replyToId) {
 | 
						|
				_fieldBarCancel.hide();
 | 
						|
				updateMouseTracking();
 | 
						|
			}
 | 
						|
		}
 | 
						|
	} else {
 | 
						|
		if (!_scroll.isHidden()) {
 | 
						|
			_kbScroll.hide();
 | 
						|
			_attachEmoji.show();
 | 
						|
			_kbHide.hide();
 | 
						|
			_kbShow.hide();
 | 
						|
			_cmdStart.show();
 | 
						|
		}
 | 
						|
		_field.setMaxHeight(st::maxFieldHeight);
 | 
						|
		_kbShown = false;
 | 
						|
		_kbReplyTo = 0;
 | 
						|
		if (!readyToForward() && (!_previewData || _previewData->pendingTill < 0) && !_replyToId && !_editMsgId) {
 | 
						|
			_fieldBarCancel.hide();
 | 
						|
			updateMouseTracking();
 | 
						|
		}
 | 
						|
	}
 | 
						|
	resizeEvent(0);
 | 
						|
	update();
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::updateToEndVisibility() {
 | 
						|
	if (_a_show.animating()) return;
 | 
						|
 | 
						|
	auto haveUnreadBelowBottom = [this](History *history) {
 | 
						|
		if (!_list || !history || history->unreadCount() <= 0) {
 | 
						|
			return false;
 | 
						|
		}
 | 
						|
		if (!history->showFrom || history->showFrom->detached()) {
 | 
						|
			return false;
 | 
						|
		}
 | 
						|
		return (_list->itemTop(history->showFrom) >= _scroll.scrollTop() + _scroll.height());
 | 
						|
	};
 | 
						|
	auto isToEndVisible = [this, &haveUnreadBelowBottom]() {
 | 
						|
		if (!_history || _firstLoadRequest) {
 | 
						|
			return false;
 | 
						|
		}
 | 
						|
		if (!_history->loadedAtBottom() || _replyReturn) {
 | 
						|
			return true;
 | 
						|
		}
 | 
						|
		if (_scroll.scrollTop() + st::wndMinHeight < _scroll.scrollTopMax()) {
 | 
						|
			return true;
 | 
						|
		}
 | 
						|
		if (haveUnreadBelowBottom(_history) || haveUnreadBelowBottom(_migrated)) {
 | 
						|
			return true;
 | 
						|
		}
 | 
						|
		return false;
 | 
						|
	};
 | 
						|
	bool toEndVisible = isToEndVisible();
 | 
						|
	if (toEndVisible && _historyToEnd->hidden()) {
 | 
						|
		_historyToEnd->showAnimated();
 | 
						|
	} else if (!toEndVisible && !_historyToEnd->hidden()) {
 | 
						|
		_historyToEnd->hideAnimated();
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::mousePressEvent(QMouseEvent *e) {
 | 
						|
	_replyForwardPressed = QRect(0, _field.y() - st::sendPadding - st::replyHeight, st::replySkip, st::replyHeight).contains(e->pos());
 | 
						|
	if (_replyForwardPressed && !_fieldBarCancel.isHidden()) {
 | 
						|
		updateField();
 | 
						|
	} else if (_inRecord && cHasAudioCapture()) {
 | 
						|
		emit audioCapture()->start();
 | 
						|
 | 
						|
		_recording = _inField = true;
 | 
						|
		updateControlsVisibility();
 | 
						|
		activate();
 | 
						|
 | 
						|
		updateField();
 | 
						|
 | 
						|
		a_recordDown.start(1);
 | 
						|
		a_recordOver.restart();
 | 
						|
		_a_record.start();
 | 
						|
	} else if (_inReplyEdit) {
 | 
						|
		Ui::showPeerHistory(_peer, _editMsgId ? _editMsgId : replyToId());
 | 
						|
	} else if (_inPinnedMsg) {
 | 
						|
		t_assert(_pinnedBar != nullptr);
 | 
						|
		Ui::showPeerHistory(_peer, _pinnedBar->msgId);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::keyPressEvent(QKeyEvent *e) {
 | 
						|
	if (!_history) return;
 | 
						|
 | 
						|
	if (e->key() == Qt::Key_Escape) {
 | 
						|
		e->ignore();
 | 
						|
	} else if (e->key() == Qt::Key_Back) {
 | 
						|
		App::main()->showBackFromStack();
 | 
						|
		emit cancelled();
 | 
						|
	} else if (e->key() == Qt::Key_PageDown) {
 | 
						|
		_scroll.keyPressEvent(e);
 | 
						|
	} else if (e->key() == Qt::Key_PageUp) {
 | 
						|
		_scroll.keyPressEvent(e);
 | 
						|
	} else if (e->key() == Qt::Key_Down) {
 | 
						|
		if (!(e->modifiers() & (Qt::ShiftModifier | Qt::MetaModifier | Qt::ControlModifier))) {
 | 
						|
			_scroll.keyPressEvent(e);
 | 
						|
		}
 | 
						|
	} else if (e->key() == Qt::Key_Up) {
 | 
						|
		if (!(e->modifiers() & (Qt::ShiftModifier | Qt::MetaModifier | Qt::ControlModifier))) {
 | 
						|
			if (_history && _history->lastSentMsg && _history->lastSentMsg->canEdit(::date(unixtime()))) {
 | 
						|
				if (_field.isEmpty() && !_editMsgId && !_replyToId) {
 | 
						|
					App::contextItem(_history->lastSentMsg);
 | 
						|
					onEditMessage();
 | 
						|
					return;
 | 
						|
				}
 | 
						|
			}
 | 
						|
			_scroll.keyPressEvent(e);
 | 
						|
		}
 | 
						|
	} else if (e->key() == Qt::Key_Return || e->key() == Qt::Key_Enter) {
 | 
						|
		onListEnterPressed();
 | 
						|
	} else {
 | 
						|
		e->ignore();
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::onFieldTabbed() {
 | 
						|
	if (!_fieldAutocomplete->isHidden()) {
 | 
						|
		_fieldAutocomplete->chooseSelected(FieldAutocomplete::ChooseMethod::ByTab);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
bool HistoryWidget::onStickerSend(DocumentData *sticker) {
 | 
						|
	return sendExistingDocument(sticker, QString());
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::onPhotoSend(PhotoData *photo) {
 | 
						|
	sendExistingPhoto(photo, QString());
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::onInlineResultSend(InlineBots::Result *result, UserData *bot) {
 | 
						|
	if (!_history || !result || !canSendMessages(_peer)) return;
 | 
						|
 | 
						|
	App::main()->readServerHistory(_history);
 | 
						|
	fastShowAtEnd(_history);
 | 
						|
 | 
						|
	uint64 randomId = rand_value<uint64>();
 | 
						|
	FullMsgId newId(_channel, clientMsgId());
 | 
						|
 | 
						|
	bool lastKeyboardUsed = lastForceReplyReplied();
 | 
						|
 | 
						|
	bool out = !_peer->isSelf(), unread = !_peer->isSelf();
 | 
						|
	MTPDmessage::Flags flags = newMessageFlags(_peer) | MTPDmessage::Flag::f_media; // unread, out
 | 
						|
	MTPmessages_SendInlineBotResult::Flags sendFlags = MTPmessages_SendInlineBotResult::Flag::f_clear_draft;
 | 
						|
	if (replyToId()) {
 | 
						|
		flags |= MTPDmessage::Flag::f_reply_to_msg_id;
 | 
						|
		sendFlags |= MTPmessages_SendInlineBotResult::Flag::f_reply_to_msg_id;
 | 
						|
	}
 | 
						|
	bool channelPost = _peer->isChannel() && !_peer->isMegagroup();
 | 
						|
	bool showFromName = !channelPost || _peer->asChannel()->addsSignature();
 | 
						|
	bool silentPost = channelPost && _silent.checked();
 | 
						|
	if (channelPost) {
 | 
						|
		flags |= MTPDmessage::Flag::f_views;
 | 
						|
		flags |= MTPDmessage::Flag::f_post;
 | 
						|
	}
 | 
						|
	if (showFromName) {
 | 
						|
		flags |= MTPDmessage::Flag::f_from_id;
 | 
						|
	}
 | 
						|
	if (silentPost) {
 | 
						|
		sendFlags |= MTPmessages_SendInlineBotResult::Flag::f_silent;
 | 
						|
	}
 | 
						|
	if (bot) {
 | 
						|
		flags |= MTPDmessage::Flag::f_via_bot_id;
 | 
						|
	}
 | 
						|
 | 
						|
	UserId messageFromId = showFromName ? MTP::authedId() : 0;
 | 
						|
	MTPint messageDate = MTP_int(unixtime());
 | 
						|
	UserId messageViaBotId = bot ? peerToUser(bot->id) : 0;
 | 
						|
	MsgId messageId = newId.msg;
 | 
						|
 | 
						|
	result->addToHistory(_history, flags, messageId, messageFromId, messageDate, messageViaBotId, replyToId());
 | 
						|
 | 
						|
	_history->sendRequestId = MTP::send(MTPmessages_SendInlineBotResult(MTP_flags(sendFlags), _peer->input, MTP_int(replyToId()), MTP_long(randomId), MTP_long(result->getQueryId()), MTP_string(result->getId())), App::main()->rpcDone(&MainWidget::sentUpdatesReceived), App::main()->rpcFail(&MainWidget::sendMessageFail), 0, 0, _history->sendRequestId);
 | 
						|
	App::main()->finishForwarding(_history, _silent.checked());
 | 
						|
	cancelReply(lastKeyboardUsed);
 | 
						|
 | 
						|
	App::historyRegRandom(randomId, newId);
 | 
						|
 | 
						|
	clearFieldText();
 | 
						|
	_saveDraftText = true;
 | 
						|
	_saveDraftStart = getms();
 | 
						|
	onDraftSave();
 | 
						|
 | 
						|
	RecentInlineBots &bots(cRefRecentInlineBots());
 | 
						|
	int32 index = bots.indexOf(bot);
 | 
						|
	if (index) {
 | 
						|
		if (index > 0) {
 | 
						|
			bots.removeAt(index);
 | 
						|
		} else if (bots.size() >= RecentInlineBotsLimit) {
 | 
						|
			bots.resize(RecentInlineBotsLimit - 1);
 | 
						|
		}
 | 
						|
		bots.push_front(bot);
 | 
						|
		Local::writeRecentHashtagsAndBots();
 | 
						|
	}
 | 
						|
 | 
						|
	if (!_fieldAutocomplete->isHidden()) _fieldAutocomplete->hideStart();
 | 
						|
	if (!_attachType->isHidden()) _attachType->hideStart();
 | 
						|
	if (!_emojiPan->isHidden()) _emojiPan->hideStart();
 | 
						|
 | 
						|
	_field.setFocus();
 | 
						|
}
 | 
						|
 | 
						|
HistoryWidget::PinnedBar::PinnedBar(MsgId msgId, HistoryWidget *parent)
 | 
						|
: msgId(msgId)
 | 
						|
, cancel(parent, st::replyCancel)
 | 
						|
, shadow(parent, st::shadowColor) {
 | 
						|
}
 | 
						|
 | 
						|
HistoryWidget::PinnedBar::~PinnedBar() {
 | 
						|
	cancel.destroyDelayed();
 | 
						|
	shadow.destroyDelayed();
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::updatePinnedBar(bool force) {
 | 
						|
	if (!_pinnedBar) {
 | 
						|
		return;
 | 
						|
	}
 | 
						|
	if (!force) {
 | 
						|
		if (_pinnedBar->msg) {
 | 
						|
			return;
 | 
						|
		}
 | 
						|
	}
 | 
						|
	t_assert(_history != nullptr);
 | 
						|
 | 
						|
	if (!_pinnedBar->msg) {
 | 
						|
		_pinnedBar->msg = App::histItemById(_history->channelId(), _pinnedBar->msgId);
 | 
						|
	}
 | 
						|
	if (_pinnedBar->msg) {
 | 
						|
		_pinnedBar->text.setText(st::msgFont, textClean(_pinnedBar->msg->notificationText()), _textDlgOptions);
 | 
						|
		update();
 | 
						|
	} else if (force) {
 | 
						|
		if (_peer && _peer->isMegagroup()) {
 | 
						|
			_peer->asChannel()->mgInfo->pinnedMsgId = 0;
 | 
						|
		}
 | 
						|
		destroyPinnedBar();
 | 
						|
		resizeEvent(0);
 | 
						|
		update();
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
bool HistoryWidget::pinnedMsgVisibilityUpdated() {
 | 
						|
	bool result = false;
 | 
						|
	MsgId pinnedMsgId = (_peer && _peer->isMegagroup()) ? _peer->asChannel()->mgInfo->pinnedMsgId : 0;
 | 
						|
	if (pinnedMsgId && !_peer->asChannel()->amCreator() && !_peer->asChannel()->amEditor()) {
 | 
						|
		Global::HiddenPinnedMessagesMap::const_iterator it = Global::HiddenPinnedMessages().constFind(_peer->id);
 | 
						|
		if (it != Global::HiddenPinnedMessages().cend()) {
 | 
						|
			if (it.value() == pinnedMsgId) {
 | 
						|
				pinnedMsgId = 0;
 | 
						|
			} else {
 | 
						|
				Global::RefHiddenPinnedMessages().remove(_peer->id);
 | 
						|
				Local::writeUserSettings();
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if (pinnedMsgId) {
 | 
						|
		if (!_pinnedBar) {
 | 
						|
			_pinnedBar = std_::make_unique<PinnedBar>(pinnedMsgId, this);
 | 
						|
			if (_a_show.animating()) {
 | 
						|
				_pinnedBar->cancel->hide();
 | 
						|
				_pinnedBar->shadow->hide();
 | 
						|
			} else {
 | 
						|
				_pinnedBar->cancel->show();
 | 
						|
				_pinnedBar->shadow->show();
 | 
						|
			}
 | 
						|
			connect(_pinnedBar->cancel, SIGNAL(clicked()), this, SLOT(onPinnedHide()));
 | 
						|
			_reportSpamPanel.raise();
 | 
						|
			_topShadow->raise();
 | 
						|
			if (_membersDropdown) {
 | 
						|
				_membersDropdown->raise();
 | 
						|
			}
 | 
						|
			_attachType->raise();
 | 
						|
			_emojiPan->raise();
 | 
						|
			_attachDragDocument->raise();
 | 
						|
			_attachDragPhoto->raise();
 | 
						|
 | 
						|
			updatePinnedBar();
 | 
						|
			result = true;
 | 
						|
 | 
						|
			if (_scroll.scrollTop() != unreadBarTop()) {
 | 
						|
				_scroll.scrollToY(_scroll.scrollTop() + st::replyHeight);
 | 
						|
			}
 | 
						|
		} else if (_pinnedBar->msgId != pinnedMsgId) {
 | 
						|
			_pinnedBar->msgId = pinnedMsgId;
 | 
						|
			_pinnedBar->msg = 0;
 | 
						|
			_pinnedBar->text.clear();
 | 
						|
			updatePinnedBar();
 | 
						|
			update();
 | 
						|
		}
 | 
						|
		if (!_pinnedBar->msg && App::api()) {
 | 
						|
			App::api()->requestMessageData(_peer->asChannel(), _pinnedBar->msgId, replyEditMessageDataCallback());
 | 
						|
		}
 | 
						|
	} else if (_pinnedBar) {
 | 
						|
		destroyPinnedBar();
 | 
						|
		result = true;
 | 
						|
		if (_scroll.scrollTop() != unreadBarTop()) {
 | 
						|
			_scroll.scrollToY(_scroll.scrollTop() - st::replyHeight);
 | 
						|
		}
 | 
						|
		resizeEvent(0);
 | 
						|
	}
 | 
						|
	return result;
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::destroyPinnedBar() {
 | 
						|
	_pinnedBar.reset();
 | 
						|
	_inPinnedMsg = false;
 | 
						|
}
 | 
						|
 | 
						|
bool HistoryWidget::sendExistingDocument(DocumentData *doc, const QString &caption) {
 | 
						|
	if (!_history || !doc || !canSendMessages(_peer)) {
 | 
						|
		return false;
 | 
						|
	}
 | 
						|
 | 
						|
	MTPInputDocument mtpInput = doc->mtpInput();
 | 
						|
	if (mtpInput.type() == mtpc_inputDocumentEmpty) {
 | 
						|
		return false;
 | 
						|
	}
 | 
						|
 | 
						|
	App::main()->readServerHistory(_history);
 | 
						|
	fastShowAtEnd(_history);
 | 
						|
 | 
						|
	uint64 randomId = rand_value<uint64>();
 | 
						|
	FullMsgId newId(_channel, clientMsgId());
 | 
						|
 | 
						|
	bool lastKeyboardUsed = lastForceReplyReplied();
 | 
						|
 | 
						|
	bool out = !_peer->isSelf(), unread = !_peer->isSelf();
 | 
						|
	MTPDmessage::Flags flags = newMessageFlags(_peer) | MTPDmessage::Flag::f_media; // unread, out
 | 
						|
	MTPmessages_SendMedia::Flags sendFlags = 0;
 | 
						|
	if (replyToId()) {
 | 
						|
		flags |= MTPDmessage::Flag::f_reply_to_msg_id;
 | 
						|
		sendFlags |= MTPmessages_SendMedia::Flag::f_reply_to_msg_id;
 | 
						|
	}
 | 
						|
	bool channelPost = _peer->isChannel() && !_peer->isMegagroup();
 | 
						|
	bool showFromName = !channelPost || _peer->asChannel()->addsSignature();
 | 
						|
	bool silentPost = channelPost && _silent.checked();
 | 
						|
	if (channelPost) {
 | 
						|
		flags |= MTPDmessage::Flag::f_views;
 | 
						|
		flags |= MTPDmessage::Flag::f_post;
 | 
						|
	}
 | 
						|
	if (showFromName) {
 | 
						|
		flags |= MTPDmessage::Flag::f_from_id;
 | 
						|
	}
 | 
						|
	if (silentPost) {
 | 
						|
		sendFlags |= MTPmessages_SendMedia::Flag::f_silent;
 | 
						|
	}
 | 
						|
	_history->addNewDocument(newId.msg, flags, 0, replyToId(), date(MTP_int(unixtime())), showFromName ? MTP::authedId() : 0, doc, caption, MTPnullMarkup);
 | 
						|
 | 
						|
	_history->sendRequestId = MTP::send(MTPmessages_SendMedia(MTP_flags(sendFlags), _peer->input, MTP_int(replyToId()), MTP_inputMediaDocument(mtpInput, MTP_string(caption)), MTP_long(randomId), MTPnullMarkup), App::main()->rpcDone(&MainWidget::sentUpdatesReceived), App::main()->rpcFail(&MainWidget::sendMessageFail), 0, 0, _history->sendRequestId);
 | 
						|
	App::main()->finishForwarding(_history, _silent.checked());
 | 
						|
	cancelReplyAfterMediaSend(lastKeyboardUsed);
 | 
						|
 | 
						|
	if (doc->sticker()) App::main()->incrementSticker(doc);
 | 
						|
 | 
						|
	App::historyRegRandom(randomId, newId);
 | 
						|
 | 
						|
	if (_fieldAutocomplete->stickersShown()) {
 | 
						|
		clearFieldText();
 | 
						|
		//_saveDraftText = true;
 | 
						|
		//_saveDraftStart = getms();
 | 
						|
		//onDraftSave();
 | 
						|
		onCloudDraftSave(); // won't be needed if SendInlineBotResult will clear the cloud draft
 | 
						|
	}
 | 
						|
 | 
						|
	if (!_fieldAutocomplete->isHidden()) _fieldAutocomplete->hideStart();
 | 
						|
	if (!_attachType->isHidden()) _attachType->hideStart();
 | 
						|
	if (!_emojiPan->isHidden()) _emojiPan->hideStart();
 | 
						|
 | 
						|
	_field.setFocus();
 | 
						|
	return true;
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::sendExistingPhoto(PhotoData *photo, const QString &caption) {
 | 
						|
	if (!_history || !photo || !canSendMessages(_peer)) return;
 | 
						|
 | 
						|
	App::main()->readServerHistory(_history);
 | 
						|
	fastShowAtEnd(_history);
 | 
						|
 | 
						|
	uint64 randomId = rand_value<uint64>();
 | 
						|
	FullMsgId newId(_channel, clientMsgId());
 | 
						|
 | 
						|
	bool lastKeyboardUsed = lastForceReplyReplied();
 | 
						|
 | 
						|
	bool out = !_peer->isSelf(), unread = !_peer->isSelf();
 | 
						|
	MTPDmessage::Flags flags = newMessageFlags(_peer) | MTPDmessage::Flag::f_media; // unread, out
 | 
						|
	MTPmessages_SendMedia::Flags sendFlags = 0;
 | 
						|
	if (replyToId()) {
 | 
						|
		flags |= MTPDmessage::Flag::f_reply_to_msg_id;
 | 
						|
		sendFlags |= MTPmessages_SendMedia::Flag::f_reply_to_msg_id;
 | 
						|
	}
 | 
						|
	bool channelPost = _peer->isChannel() && !_peer->isMegagroup();
 | 
						|
	bool showFromName = !channelPost || _peer->asChannel()->addsSignature();
 | 
						|
	bool silentPost = channelPost && _silent.checked();
 | 
						|
	if (channelPost) {
 | 
						|
		flags |= MTPDmessage::Flag::f_views;
 | 
						|
		flags |= MTPDmessage::Flag::f_post;
 | 
						|
	}
 | 
						|
	if (showFromName) {
 | 
						|
		flags |= MTPDmessage::Flag::f_from_id;
 | 
						|
	}
 | 
						|
	if (silentPost) {
 | 
						|
		sendFlags |= MTPmessages_SendMedia::Flag::f_silent;
 | 
						|
	}
 | 
						|
	_history->addNewPhoto(newId.msg, flags, 0, replyToId(), date(MTP_int(unixtime())), showFromName ? MTP::authedId() : 0, photo, caption, MTPnullMarkup);
 | 
						|
 | 
						|
	_history->sendRequestId = MTP::send(MTPmessages_SendMedia(MTP_flags(sendFlags), _peer->input, MTP_int(replyToId()), MTP_inputMediaPhoto(MTP_inputPhoto(MTP_long(photo->id), MTP_long(photo->access)), MTP_string(caption)), MTP_long(randomId), MTPnullMarkup), App::main()->rpcDone(&MainWidget::sentUpdatesReceived), App::main()->rpcFail(&MainWidget::sendMessageFail), 0, 0, _history->sendRequestId);
 | 
						|
	App::main()->finishForwarding(_history, _silent.checked());
 | 
						|
	cancelReplyAfterMediaSend(lastKeyboardUsed);
 | 
						|
 | 
						|
	App::historyRegRandom(randomId, newId);
 | 
						|
 | 
						|
	if (!_fieldAutocomplete->isHidden()) _fieldAutocomplete->hideStart();
 | 
						|
	if (!_attachType->isHidden()) _attachType->hideStart();
 | 
						|
	if (!_emojiPan->isHidden()) _emojiPan->hideStart();
 | 
						|
 | 
						|
	_field.setFocus();
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::setFieldText(const TextWithTags &textWithTags, TextUpdateEvents events, FlatTextarea::UndoHistoryAction undoHistoryAction) {
 | 
						|
	_textUpdateEvents = events;
 | 
						|
	_field.setTextWithTags(textWithTags, undoHistoryAction);
 | 
						|
	_field.moveCursor(QTextCursor::End);
 | 
						|
	_textUpdateEvents = TextUpdateEvent::SaveDraft | TextUpdateEvent::SendTyping;
 | 
						|
 | 
						|
	_previewCancelled = false;
 | 
						|
	_previewData = nullptr;
 | 
						|
	if (_previewRequest) {
 | 
						|
		MTP::cancel(_previewRequest);
 | 
						|
		_previewRequest = 0;
 | 
						|
	}
 | 
						|
	_previewLinks.clear();
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::onReplyToMessage() {
 | 
						|
	HistoryItem *to = App::contextItem();
 | 
						|
	if (!to || to->id <= 0 || !_canSendMessages) return;
 | 
						|
 | 
						|
	if (to->history() == _migrated) {
 | 
						|
		if (to->isGroupMigrate() && !_history->isEmpty() && _history->blocks.front()->items.front()->isGroupMigrate() && _history != _migrated) {
 | 
						|
			App::contextItem(_history->blocks.front()->items.front());
 | 
						|
			onReplyToMessage();
 | 
						|
			App::contextItem(to);
 | 
						|
		} else {
 | 
						|
			LayerWidget *box = nullptr;
 | 
						|
			if (to->type() != HistoryItemMsg || to->serviceMsg()) {
 | 
						|
				box = new InformBox(lang(lng_reply_cant));
 | 
						|
			} else {
 | 
						|
				box = new ConfirmBox(lang(lng_reply_cant_forward), lang(lng_selected_forward));
 | 
						|
				connect(box, SIGNAL(confirmed()), this, SLOT(onForwardHere()));
 | 
						|
			}
 | 
						|
			Ui::showLayer(box);
 | 
						|
		}
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	App::main()->cancelForwarding();
 | 
						|
 | 
						|
	if (_editMsgId) {
 | 
						|
		if (auto localDraft = _history->localDraft()) {
 | 
						|
			localDraft->msgId = to->id;
 | 
						|
		} else {
 | 
						|
			_history->setLocalDraft(std_::make_unique<Data::Draft>(TextWithTags(), to->id, MessageCursor(), false));
 | 
						|
		}
 | 
						|
	} else {
 | 
						|
		_replyEditMsg = to;
 | 
						|
		_replyToId = to->id;
 | 
						|
		_replyEditMsgText.setText(st::msgFont, textClean(_replyEditMsg->inReplyText()), _textDlgOptions);
 | 
						|
 | 
						|
		updateBotKeyboard();
 | 
						|
 | 
						|
		if (!_field.isHidden()) _fieldBarCancel.show();
 | 
						|
		updateMouseTracking();
 | 
						|
		updateReplyToName();
 | 
						|
		resizeEvent(0);
 | 
						|
		updateField();
 | 
						|
	}
 | 
						|
 | 
						|
	_saveDraftText = true;
 | 
						|
	_saveDraftStart = getms();
 | 
						|
	onDraftSave();
 | 
						|
 | 
						|
	_field.setFocus();
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::onEditMessage() {
 | 
						|
	HistoryItem *to = App::contextItem();
 | 
						|
	if (!to) return;
 | 
						|
 | 
						|
	EditCaptionBox *box = new EditCaptionBox(to);
 | 
						|
	if (box->captionFound()) {
 | 
						|
		Ui::showLayer(box);
 | 
						|
	} else {
 | 
						|
		delete box;
 | 
						|
 | 
						|
		if (!_editMsgId) {
 | 
						|
			if (_replyToId || !_field.isEmpty()) {
 | 
						|
				_history->setLocalDraft(std_::make_unique<Data::Draft>(_field, _replyToId, _previewCancelled));
 | 
						|
			} else {
 | 
						|
				_history->clearLocalDraft();
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		auto original = to->originalText();
 | 
						|
		auto editText = textApplyEntities(original.text, original.entities);
 | 
						|
		auto editTags = textTagsFromEntities(original.entities);
 | 
						|
		TextWithTags editData = { editText, editTags };
 | 
						|
		MessageCursor cursor = { editText.size(), editText.size(), QFIXED_MAX };
 | 
						|
		_history->setEditDraft(std_::make_unique<Data::Draft>(editData, to->id, cursor, false));
 | 
						|
		applyDraft(false);
 | 
						|
 | 
						|
		_previewData = nullptr;
 | 
						|
		if (auto media = to->getMedia()) {
 | 
						|
			if (media->type() == MediaTypeWebPage) {
 | 
						|
				_previewData = static_cast<HistoryWebPage*>(media)->webpage();
 | 
						|
				updatePreview();
 | 
						|
			}
 | 
						|
		}
 | 
						|
		if (!_previewData) {
 | 
						|
			onPreviewParse();
 | 
						|
		}
 | 
						|
 | 
						|
		updateBotKeyboard();
 | 
						|
 | 
						|
		if (!_field.isHidden()) _fieldBarCancel.show();
 | 
						|
		updateFieldPlaceholder();
 | 
						|
		updateMouseTracking();
 | 
						|
		updateReplyToName();
 | 
						|
		resizeEvent(nullptr);
 | 
						|
		updateField();
 | 
						|
 | 
						|
		_saveDraftText = true;
 | 
						|
		_saveDraftStart = getms();
 | 
						|
		onDraftSave();
 | 
						|
 | 
						|
		_field.setFocus();
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::onPinMessage() {
 | 
						|
	HistoryItem *to = App::contextItem();
 | 
						|
	if (!to || !to->canPin() || !_peer || !_peer->isMegagroup()) return;
 | 
						|
 | 
						|
	Ui::showLayer(new PinMessageBox(_peer->asChannel(), to->id));
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::onUnpinMessage() {
 | 
						|
	if (!_peer || !_peer->isMegagroup()) return;
 | 
						|
 | 
						|
	ConfirmBox *box = new ConfirmBox(lang(lng_pinned_unpin_sure), lang(lng_pinned_unpin));
 | 
						|
	connect(box, SIGNAL(confirmed()), this, SLOT(onUnpinMessageSure()));
 | 
						|
	Ui::showLayer(box);
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::onUnpinMessageSure() {
 | 
						|
	if (!_peer || !_peer->isMegagroup()) return;
 | 
						|
 | 
						|
	_peer->asChannel()->mgInfo->pinnedMsgId = 0;
 | 
						|
	if (pinnedMsgVisibilityUpdated()) {
 | 
						|
		resizeEvent(0);
 | 
						|
		update();
 | 
						|
	}
 | 
						|
 | 
						|
	Ui::hideLayer();
 | 
						|
	MTPchannels_UpdatePinnedMessage::Flags flags = 0;
 | 
						|
	MTP::send(MTPchannels_UpdatePinnedMessage(MTP_flags(flags), _peer->asChannel()->inputChannel, MTP_int(0)), rpcDone(&HistoryWidget::unpinDone));
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::unpinDone(const MTPUpdates &updates) {
 | 
						|
	if (App::main()) {
 | 
						|
		App::main()->sentUpdatesReceived(updates);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::onPinnedHide() {
 | 
						|
	if (!_peer || !_peer->isMegagroup()) return;
 | 
						|
	if (!_peer->asChannel()->mgInfo->pinnedMsgId) {
 | 
						|
		if (pinnedMsgVisibilityUpdated()) {
 | 
						|
			resizeEvent(0);
 | 
						|
			update();
 | 
						|
		}
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	if (_peer->asChannel()->amCreator() || _peer->asChannel()->amEditor()) {
 | 
						|
		onUnpinMessage();
 | 
						|
	} else {
 | 
						|
		Global::RefHiddenPinnedMessages().insert(_peer->id, _peer->asChannel()->mgInfo->pinnedMsgId);
 | 
						|
		Local::writeUserSettings();
 | 
						|
		if (pinnedMsgVisibilityUpdated()) {
 | 
						|
			resizeEvent(0);
 | 
						|
			update();
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::onCopyPostLink() {
 | 
						|
	HistoryItem *to = App::contextItem();
 | 
						|
	if (!to || !to->hasDirectLink()) return;
 | 
						|
 | 
						|
	QApplication::clipboard()->setText(to->directLink());
 | 
						|
}
 | 
						|
 | 
						|
bool HistoryWidget::lastForceReplyReplied(const FullMsgId &replyTo) const {
 | 
						|
	if (replyTo.msg > 0 && replyTo.channel != _channel) return false;
 | 
						|
	return _keyboard.forceReply() && _keyboard.forMsgId() == FullMsgId(_channel, _history->lastKeyboardId) && _keyboard.forMsgId().msg == (replyTo.msg < 0 ? replyToId() : replyTo.msg);
 | 
						|
}
 | 
						|
 | 
						|
bool HistoryWidget::cancelReply(bool lastKeyboardUsed) {
 | 
						|
	bool wasReply = false;
 | 
						|
	if (_replyToId) {
 | 
						|
		wasReply = true;
 | 
						|
 | 
						|
		_replyEditMsg = nullptr;
 | 
						|
		_replyToId = 0;
 | 
						|
		mouseMoveEvent(0);
 | 
						|
		if (!readyToForward() && (!_previewData || _previewData->pendingTill < 0) && !_kbReplyTo) {
 | 
						|
			_fieldBarCancel.hide();
 | 
						|
			updateMouseTracking();
 | 
						|
		}
 | 
						|
 | 
						|
		updateBotKeyboard();
 | 
						|
 | 
						|
		resizeEvent(0);
 | 
						|
		update();
 | 
						|
	} else if (auto localDraft = (_history ? _history->localDraft() : nullptr)) {
 | 
						|
		if (localDraft->msgId) {
 | 
						|
			if (localDraft->textWithTags.text.isEmpty()) {
 | 
						|
				_history->clearLocalDraft();
 | 
						|
			} else {
 | 
						|
				localDraft->msgId = 0;
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if (wasReply) {
 | 
						|
		_saveDraftText = true;
 | 
						|
		_saveDraftStart = getms();
 | 
						|
		onDraftSave();
 | 
						|
	}
 | 
						|
	if (!_editMsgId && _keyboard.singleUse() && _keyboard.forceReply() && lastKeyboardUsed) {
 | 
						|
		if (_kbReplyTo) {
 | 
						|
			onKbToggle(false);
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return wasReply;
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::cancelReplyAfterMediaSend(bool lastKeyboardUsed) {
 | 
						|
	if (cancelReply(lastKeyboardUsed)) {
 | 
						|
		onCloudDraftSave();
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
int HistoryWidget::countMembersDropdownHeightMax() const {
 | 
						|
	int result = height() - st::membersInnerDropdown.padding.top() - st::membersInnerDropdown.padding.bottom();
 | 
						|
	result -= _attachEmoji.height();
 | 
						|
	accumulate_min(result, st::membersInnerHeightMax);
 | 
						|
	return result;
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::cancelEdit() {
 | 
						|
	if (!_editMsgId) return;
 | 
						|
 | 
						|
	_replyEditMsg = nullptr;
 | 
						|
	_editMsgId = 0;
 | 
						|
	_history->clearEditDraft();
 | 
						|
	applyDraft();
 | 
						|
 | 
						|
	if (_saveEditMsgRequestId) {
 | 
						|
		MTP::cancel(_saveEditMsgRequestId);
 | 
						|
		_saveEditMsgRequestId = 0;
 | 
						|
	}
 | 
						|
 | 
						|
	_saveDraftText = true;
 | 
						|
	_saveDraftStart = getms();
 | 
						|
	onDraftSave();
 | 
						|
 | 
						|
	mouseMoveEvent(nullptr);
 | 
						|
	if (!readyToForward() && (!_previewData || _previewData->pendingTill < 0) && !replyToId()) {
 | 
						|
		_fieldBarCancel.hide();
 | 
						|
		updateMouseTracking();
 | 
						|
	}
 | 
						|
 | 
						|
	auto old = _textUpdateEvents;
 | 
						|
	_textUpdateEvents = 0;
 | 
						|
	onTextChange();
 | 
						|
	_textUpdateEvents = old;
 | 
						|
 | 
						|
	updateBotKeyboard();
 | 
						|
	updateFieldPlaceholder();
 | 
						|
 | 
						|
	resizeEvent(nullptr);
 | 
						|
	update();
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::cancelForwarding() {
 | 
						|
	updateControlsVisibility();
 | 
						|
	updateControlsGeometry();
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::onFieldBarCancel() {
 | 
						|
	Ui::hideLayer();
 | 
						|
	_replyForwardPressed = false;
 | 
						|
	if (_previewData && _previewData->pendingTill >= 0) {
 | 
						|
		_previewCancelled = true;
 | 
						|
		previewCancel();
 | 
						|
 | 
						|
		_saveDraftText = true;
 | 
						|
		_saveDraftStart = getms();
 | 
						|
		onDraftSave();
 | 
						|
	} else if (_editMsgId) {
 | 
						|
		cancelEdit();
 | 
						|
	} else if (readyToForward()) {
 | 
						|
		App::main()->cancelForwarding();
 | 
						|
	} else if (_replyToId) {
 | 
						|
		cancelReply();
 | 
						|
	} else if (_kbReplyTo) {
 | 
						|
		onKbToggle();
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::onStickerPackInfo() {
 | 
						|
	if (!App::contextItem()) return;
 | 
						|
 | 
						|
	if (HistoryMedia *media = App::contextItem()->getMedia()) {
 | 
						|
		if (DocumentData *doc = media->getDocument()) {
 | 
						|
			if (StickerData *sticker = doc->sticker()) {
 | 
						|
				if (sticker->set.type() != mtpc_inputStickerSetEmpty) {
 | 
						|
					App::main()->stickersBox(sticker->set);
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::previewCancel() {
 | 
						|
	MTP::cancel(_previewRequest);
 | 
						|
	_previewRequest = 0;
 | 
						|
	_previewData = nullptr;
 | 
						|
	_previewLinks.clear();
 | 
						|
	updatePreview();
 | 
						|
	if (!_editMsgId && !_replyToId && !readyToForward() && !_kbReplyTo) {
 | 
						|
		_fieldBarCancel.hide();
 | 
						|
		updateMouseTracking();
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::onPreviewParse() {
 | 
						|
	if (_previewCancelled) return;
 | 
						|
	_field.parseLinks();
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::onPreviewCheck() {
 | 
						|
	if (_previewCancelled) return;
 | 
						|
	QStringList linksList = _field.linksList();
 | 
						|
	QString newLinks = linksList.join(' ');
 | 
						|
	if (newLinks != _previewLinks) {
 | 
						|
		MTP::cancel(_previewRequest);
 | 
						|
		_previewLinks = newLinks;
 | 
						|
		if (_previewLinks.isEmpty()) {
 | 
						|
			if (_previewData && _previewData->pendingTill >= 0) previewCancel();
 | 
						|
		} else {
 | 
						|
			PreviewCache::const_iterator i = _previewCache.constFind(_previewLinks);
 | 
						|
			if (i == _previewCache.cend()) {
 | 
						|
				_previewRequest = MTP::send(MTPmessages_GetWebPagePreview(MTP_string(_previewLinks)), rpcDone(&HistoryWidget::gotPreview, _previewLinks));
 | 
						|
			} else if (i.value()) {
 | 
						|
				_previewData = App::webPage(i.value());
 | 
						|
				updatePreview();
 | 
						|
			} else {
 | 
						|
				if (_previewData && _previewData->pendingTill >= 0) previewCancel();
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::onPreviewTimeout() {
 | 
						|
	if (_previewData && _previewData->pendingTill > 0 && !_previewLinks.isEmpty()) {
 | 
						|
		_previewRequest = MTP::send(MTPmessages_GetWebPagePreview(MTP_string(_previewLinks)), rpcDone(&HistoryWidget::gotPreview, _previewLinks));
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::gotPreview(QString links, const MTPMessageMedia &result, mtpRequestId req) {
 | 
						|
	if (req == _previewRequest) {
 | 
						|
		_previewRequest = 0;
 | 
						|
	}
 | 
						|
	if (result.type() == mtpc_messageMediaWebPage) {
 | 
						|
		auto data = App::feedWebPage(result.c_messageMediaWebPage().vwebpage);
 | 
						|
		_previewCache.insert(links, data->id);
 | 
						|
		if (data->pendingTill > 0 && data->pendingTill <= unixtime()) {
 | 
						|
			data->pendingTill = -1;
 | 
						|
		}
 | 
						|
		if (links == _previewLinks && !_previewCancelled) {
 | 
						|
			_previewData = (data->id && data->pendingTill >= 0) ? data : 0;
 | 
						|
			updatePreview();
 | 
						|
		}
 | 
						|
		if (App::main()) App::main()->webPagesOrGamesUpdate();
 | 
						|
	} else if (result.type() == mtpc_messageMediaEmpty) {
 | 
						|
		_previewCache.insert(links, 0);
 | 
						|
		if (links == _previewLinks && !_previewCancelled) {
 | 
						|
			_previewData = 0;
 | 
						|
			updatePreview();
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::updatePreview() {
 | 
						|
	_previewTimer.stop();
 | 
						|
	if (_previewData && _previewData->pendingTill >= 0) {
 | 
						|
		_fieldBarCancel.show();
 | 
						|
		updateMouseTracking();
 | 
						|
		if (_previewData->pendingTill) {
 | 
						|
			_previewTitle.setText(st::msgServiceNameFont, lang(lng_preview_loading), _textNameOptions);
 | 
						|
#ifndef OS_MAC_OLD
 | 
						|
			auto linkText = _previewLinks.splitRef(' ').at(0).toString();
 | 
						|
#else // OS_MAC_OLD
 | 
						|
			auto linkText = _previewLinks.split(' ').at(0);
 | 
						|
#endif // OS_MAC_OLD
 | 
						|
			_previewDescription.setText(st::msgFont, textClean(linkText), _textDlgOptions);
 | 
						|
 | 
						|
			int32 t = (_previewData->pendingTill - unixtime()) * 1000;
 | 
						|
			if (t <= 0) t = 1;
 | 
						|
			_previewTimer.start(t);
 | 
						|
		} else {
 | 
						|
			QString title, desc;
 | 
						|
			if (_previewData->siteName.isEmpty()) {
 | 
						|
				if (_previewData->title.isEmpty()) {
 | 
						|
					if (_previewData->description.isEmpty()) {
 | 
						|
						title = _previewData->author;
 | 
						|
						desc = ((_previewData->document && !_previewData->document->name.isEmpty()) ? _previewData->document->name : _previewData->url);
 | 
						|
					} else {
 | 
						|
						title = _previewData->description;
 | 
						|
						desc = _previewData->author.isEmpty() ? ((_previewData->document && !_previewData->document->name.isEmpty()) ? _previewData->document->name : _previewData->url) : _previewData->author;
 | 
						|
					}
 | 
						|
				} else {
 | 
						|
					title = _previewData->title;
 | 
						|
					desc = _previewData->description.isEmpty() ? (_previewData->author.isEmpty() ? ((_previewData->document && !_previewData->document->name.isEmpty()) ? _previewData->document->name : _previewData->url) : _previewData->author) : _previewData->description;
 | 
						|
				}
 | 
						|
			} else {
 | 
						|
				title = _previewData->siteName;
 | 
						|
				desc = _previewData->title.isEmpty() ? (_previewData->description.isEmpty() ? (_previewData->author.isEmpty() ? ((_previewData->document && !_previewData->document->name.isEmpty()) ? _previewData->document->name : _previewData->url) : _previewData->author) : _previewData->description) : _previewData->title;
 | 
						|
			}
 | 
						|
			if (title.isEmpty()) {
 | 
						|
				if (_previewData->document) {
 | 
						|
					title = lang(lng_attach_file);
 | 
						|
				} else if (_previewData->photo) {
 | 
						|
					title = lang(lng_attach_photo);
 | 
						|
				}
 | 
						|
			}
 | 
						|
			_previewTitle.setText(st::msgServiceNameFont, title, _textNameOptions);
 | 
						|
			_previewDescription.setText(st::msgFont, textClean(desc), _textDlgOptions);
 | 
						|
		}
 | 
						|
	} else if (!readyToForward() && !replyToId() && !_editMsgId) {
 | 
						|
		_fieldBarCancel.hide();
 | 
						|
		updateMouseTracking();
 | 
						|
	}
 | 
						|
	resizeEvent(0);
 | 
						|
	update();
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::onCancel() {
 | 
						|
	if (_inlineBotCancel) {
 | 
						|
		onInlineBotCancel();
 | 
						|
	} else if (_editMsgId) {
 | 
						|
		auto original = _replyEditMsg ? _replyEditMsg->originalText() : TextWithEntities();
 | 
						|
		auto editText = textApplyEntities(original.text, original.entities);
 | 
						|
		auto editTags = textTagsFromEntities(original.entities);
 | 
						|
		TextWithTags editData = { editText, editTags };
 | 
						|
		if (_replyEditMsg && editData != _field.getTextWithTags()) {
 | 
						|
			auto box = new ConfirmBox(lang(lng_cancel_edit_post_sure), lang(lng_cancel_edit_post_yes), st::defaultBoxButton, lang(lng_cancel_edit_post_no));
 | 
						|
			connect(box, SIGNAL(confirmed()), this, SLOT(onFieldBarCancel()));
 | 
						|
			Ui::showLayer(box);
 | 
						|
		} else {
 | 
						|
			onFieldBarCancel();
 | 
						|
		}
 | 
						|
	} else if (!_fieldAutocomplete->isHidden()) {
 | 
						|
		_fieldAutocomplete->hideStart();
 | 
						|
	} else  {
 | 
						|
		App::main()->showBackFromStack();
 | 
						|
		emit cancelled();
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::onFullPeerUpdated(PeerData *data) {
 | 
						|
	if (_list && data == _peer) {
 | 
						|
		bool newCanSendMessages = canSendMessages(_peer);
 | 
						|
		if (newCanSendMessages != _canSendMessages) {
 | 
						|
			_canSendMessages = newCanSendMessages;
 | 
						|
			if (!_canSendMessages) {
 | 
						|
				cancelReply();
 | 
						|
			}
 | 
						|
			updateControlsVisibility();
 | 
						|
		}
 | 
						|
		onCheckFieldAutocomplete();
 | 
						|
		updateReportSpamStatus();
 | 
						|
		_list->updateBotInfo();
 | 
						|
	}
 | 
						|
	if (updateCmdStartShown()) {
 | 
						|
		updateControlsVisibility();
 | 
						|
		updateControlsGeometry();
 | 
						|
	} else if (!_scroll.isHidden() && _unblock.isHidden() == isBlocked()) {
 | 
						|
		updateControlsVisibility();
 | 
						|
		updateControlsGeometry();
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::peerUpdated(PeerData *data) {
 | 
						|
	if (data && data == _peer) {
 | 
						|
		if (data->migrateTo()) {
 | 
						|
			Ui::showPeerHistory(data->migrateTo(), ShowAtUnreadMsgId);
 | 
						|
			QTimer::singleShot(ReloadChannelMembersTimeout, App::api(), SLOT(delayedRequestParticipantsCount()));
 | 
						|
			return;
 | 
						|
		}
 | 
						|
		QString restriction = _peer->restrictionReason();
 | 
						|
		if (!restriction.isEmpty()) {
 | 
						|
			App::main()->showBackFromStack();
 | 
						|
			Ui::showLayer(new InformBox(restriction));
 | 
						|
			return;
 | 
						|
		}
 | 
						|
		bool resize = false;
 | 
						|
		if (pinnedMsgVisibilityUpdated()) {
 | 
						|
			resize = true;
 | 
						|
		}
 | 
						|
		updateListSize();
 | 
						|
		if (_peer->isChannel()) updateReportSpamStatus();
 | 
						|
		if (App::api()) {
 | 
						|
			if (data->isChat() && data->asChat()->noParticipantInfo()) {
 | 
						|
				App::api()->requestFullPeer(data);
 | 
						|
			} else if (data->isUser() && data->asUser()->blockStatus() == UserData::BlockStatus::Unknown) {
 | 
						|
				App::api()->requestFullPeer(data);
 | 
						|
			} else if (data->isMegagroup() && !data->asChannel()->mgInfo->botStatus) {
 | 
						|
				App::api()->requestBots(data->asChannel());
 | 
						|
			}
 | 
						|
		}
 | 
						|
		if (!_a_show.animating()) {
 | 
						|
			if (_unblock.isHidden() == isBlocked() || (!isBlocked() && _joinChannel.isHidden() == isJoinChannel())) {
 | 
						|
				resize = true;
 | 
						|
			}
 | 
						|
			bool newCanSendMessages = canSendMessages(_peer);
 | 
						|
			if (newCanSendMessages != _canSendMessages) {
 | 
						|
				_canSendMessages = newCanSendMessages;
 | 
						|
				if (!_canSendMessages) {
 | 
						|
					cancelReply();
 | 
						|
				}
 | 
						|
				resize = true;
 | 
						|
			}
 | 
						|
			updateControlsVisibility();
 | 
						|
			if (resize) {
 | 
						|
				updateControlsGeometry();
 | 
						|
			}
 | 
						|
		}
 | 
						|
		App::main()->updateOnlineDisplay();
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::onForwardSelected() {
 | 
						|
	if (!_list) return;
 | 
						|
	App::main()->forwardLayer(true);
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::onDeleteSelected() {
 | 
						|
	if (!_list) return;
 | 
						|
 | 
						|
	SelectedItemSet sel;
 | 
						|
	_list->fillSelectedItems(sel);
 | 
						|
	if (sel.isEmpty()) return;
 | 
						|
 | 
						|
	App::main()->deleteLayer(sel.size());
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::onDeleteSelectedSure() {
 | 
						|
	Ui::hideLayer();
 | 
						|
	if (!_list) return;
 | 
						|
 | 
						|
	SelectedItemSet sel;
 | 
						|
	_list->fillSelectedItems(sel);
 | 
						|
	if (sel.isEmpty()) return;
 | 
						|
 | 
						|
	QMap<PeerData*, QVector<MTPint> > ids;
 | 
						|
	for (SelectedItemSet::const_iterator i = sel.cbegin(), e = sel.cend(); i != e; ++i) {
 | 
						|
		if (i.value()->id > 0) {
 | 
						|
			ids[i.value()->history()->peer].push_back(MTP_int(i.value()->id));
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	onClearSelected();
 | 
						|
	for (SelectedItemSet::const_iterator i = sel.cbegin(), e = sel.cend(); i != e; ++i) {
 | 
						|
		i.value()->destroy();
 | 
						|
	}
 | 
						|
 | 
						|
	for (QMap<PeerData*, QVector<MTPint> >::const_iterator i = ids.cbegin(), e = ids.cend(); i != e; ++i) {
 | 
						|
		App::main()->deleteMessages(i.key(), i.value());
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::onDeleteContextSure() {
 | 
						|
	Ui::hideLayer();
 | 
						|
 | 
						|
	HistoryItem *item = App::contextItem();
 | 
						|
	if (!item || item->type() != HistoryItemMsg) {
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	QVector<MTPint> toDelete(1, MTP_int(item->id));
 | 
						|
	History *h = item->history();
 | 
						|
	bool wasOnServer = (item->id > 0), wasLast = (h->lastMsg == item);
 | 
						|
	item->destroy();
 | 
						|
 | 
						|
	if (!wasOnServer && wasLast && !h->lastMsg) {
 | 
						|
		App::main()->checkPeerHistory(h->peer);
 | 
						|
	}
 | 
						|
 | 
						|
	if (wasOnServer) {
 | 
						|
		App::main()->deleteMessages(h->peer, toDelete);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::onListEscapePressed() {
 | 
						|
	if (_selCount && _list) {
 | 
						|
		onClearSelected();
 | 
						|
	} else {
 | 
						|
		onCancel();
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::onListEnterPressed() {
 | 
						|
	if (!_botStart.isHidden()) {
 | 
						|
		onBotStart();
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::onClearSelected() {
 | 
						|
	if (_list) _list->clearSelectedItems();
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::onAnimActiveStep() {
 | 
						|
	if (!_history || !_activeAnimMsgId || (_activeAnimMsgId < 0 && (!_migrated || -_activeAnimMsgId >= ServerMaxMsgId))) {
 | 
						|
		return _animActiveTimer.stop();
 | 
						|
	}
 | 
						|
 | 
						|
	HistoryItem *item = (_activeAnimMsgId < 0 && -_activeAnimMsgId < ServerMaxMsgId && _migrated) ? App::histItemById(_migrated->channelId(), -_activeAnimMsgId) : App::histItemById(_channel, _activeAnimMsgId);
 | 
						|
	if (!item || item->detached()) return _animActiveTimer.stop();
 | 
						|
 | 
						|
	if (getms() - _animActiveStart > st::activeFadeInDuration + st::activeFadeOutDuration) {
 | 
						|
		stopAnimActive();
 | 
						|
	} else {
 | 
						|
		Ui::repaintHistoryItem(item);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
uint64 HistoryWidget::animActiveTimeStart(const HistoryItem *msg) const {
 | 
						|
	if (!msg) return 0;
 | 
						|
	if ((msg->history() == _history && msg->id == _activeAnimMsgId) || (_migrated && msg->history() == _migrated && msg->id == -_activeAnimMsgId)) {
 | 
						|
		return _animActiveTimer.isActive() ? _animActiveStart : 0;
 | 
						|
	}
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::stopAnimActive() {
 | 
						|
	_animActiveTimer.stop();
 | 
						|
	_activeAnimMsgId = 0;
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::fillSelectedItems(SelectedItemSet &sel, bool forDelete) {
 | 
						|
	if (_list) _list->fillSelectedItems(sel, forDelete);
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::updateTopBarSelection() {
 | 
						|
	if (!_list) {
 | 
						|
		App::main()->topBar()->showSelected(0);
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	int32 selectedForForward, selectedForDelete;
 | 
						|
	_list->getSelectionState(selectedForForward, selectedForDelete);
 | 
						|
	_selCount = selectedForForward ? selectedForForward : selectedForDelete;
 | 
						|
	App::main()->topBar()->showSelected(_selCount > 0 ? _selCount : 0, (selectedForDelete == selectedForForward));
 | 
						|
	updateControlsVisibility();
 | 
						|
	updateListSize();
 | 
						|
	if (!Ui::isLayerShown() && !App::passcoded()) {
 | 
						|
		if (_selCount || (_list && _list->wasSelectedText()) || _recording || isBotStart() || isBlocked() || !_canSendMessages) {
 | 
						|
			_list->setFocus();
 | 
						|
		} else {
 | 
						|
			_field.setFocus();
 | 
						|
		}
 | 
						|
	}
 | 
						|
	App::main()->topBar()->update();
 | 
						|
	update();
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::messageDataReceived(ChannelData *channel, MsgId msgId) {
 | 
						|
	if (!_peer || _peer->asChannel() != channel || !msgId) return;
 | 
						|
	if (_editMsgId == msgId || _replyToId == msgId) {
 | 
						|
		updateReplyEditTexts(true);
 | 
						|
	}
 | 
						|
	if (_pinnedBar && _pinnedBar->msgId == msgId) {
 | 
						|
		updatePinnedBar(true);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::updateReplyEditTexts(bool force) {
 | 
						|
	if (!force) {
 | 
						|
		if (_replyEditMsg || (!_editMsgId && !_replyToId)) {
 | 
						|
			return;
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if (!_replyEditMsg) {
 | 
						|
		_replyEditMsg = App::histItemById(_channel, _editMsgId ? _editMsgId : _replyToId);
 | 
						|
	}
 | 
						|
	if (_replyEditMsg) {
 | 
						|
		_replyEditMsgText.setText(st::msgFont, textClean(_replyEditMsg->inReplyText()), _textDlgOptions);
 | 
						|
 | 
						|
		updateBotKeyboard();
 | 
						|
 | 
						|
		if (!_field.isHidden() || _recording) {
 | 
						|
			_fieldBarCancel.show();
 | 
						|
			updateMouseTracking();
 | 
						|
		}
 | 
						|
		updateReplyToName();
 | 
						|
		updateField();
 | 
						|
	} else if (force) {
 | 
						|
		if (_editMsgId) {
 | 
						|
			cancelEdit();
 | 
						|
		} else {
 | 
						|
			cancelReply();
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::updateForwarding(bool force) {
 | 
						|
	if (readyToForward()) {
 | 
						|
		updateControlsVisibility();
 | 
						|
	} else {
 | 
						|
		updateControlsGeometry();
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::updateReplyToName() {
 | 
						|
	if (_editMsgId) return;
 | 
						|
	if (!_replyEditMsg && (_replyToId || !_kbReplyTo)) return;
 | 
						|
	_replyToName.setText(st::msgServiceNameFont, App::peerName((_replyEditMsg ? _replyEditMsg : _kbReplyTo)->author()), _textNameOptions);
 | 
						|
	_replyToNameVersion = (_replyEditMsg ? _replyEditMsg : _kbReplyTo)->author()->nameVersion;
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::updateField() {
 | 
						|
	int32 fy = _scroll.y() + _scroll.height();
 | 
						|
	update(0, fy, width(), height() - fy);
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::drawField(Painter &p, const QRect &rect) {
 | 
						|
	int32 backy = _field.y() - st::sendPadding, backh = _field.height() + 2 * st::sendPadding;
 | 
						|
	Text *from = 0, *text = 0;
 | 
						|
	bool serviceColor = false, hasForward = readyToForward();
 | 
						|
	ImagePtr preview;
 | 
						|
	HistoryItem *drawMsgText = (_editMsgId || _replyToId) ? _replyEditMsg : _kbReplyTo;
 | 
						|
	if (_editMsgId || _replyToId || (!hasForward && _kbReplyTo)) {
 | 
						|
		if (!_editMsgId && drawMsgText && drawMsgText->author()->nameVersion > _replyToNameVersion) {
 | 
						|
			updateReplyToName();
 | 
						|
		}
 | 
						|
		backy -= st::replyHeight;
 | 
						|
		backh += st::replyHeight;
 | 
						|
	} else if (hasForward) {
 | 
						|
		App::main()->fillForwardingInfo(from, text, serviceColor, preview);
 | 
						|
		backy -= st::replyHeight;
 | 
						|
		backh += st::replyHeight;
 | 
						|
	} else if (_previewData && _previewData->pendingTill >= 0) {
 | 
						|
		backy -= st::replyHeight;
 | 
						|
		backh += st::replyHeight;
 | 
						|
	}
 | 
						|
	bool drawPreview = (_previewData && _previewData->pendingTill >= 0) && !_replyForwardPressed;
 | 
						|
	p.fillRect(0, backy, width(), backh, st::taMsgField.bgColor);
 | 
						|
	if (_editMsgId || _replyToId || (!hasForward && _kbReplyTo)) {
 | 
						|
		int32 replyLeft = st::replySkip;
 | 
						|
		p.drawSprite(QPoint(st::replyIconPos.x(), backy + st::replyIconPos.y()), _editMsgId ? st::editIcon : st::replyIcon);
 | 
						|
		if (!drawPreview) {
 | 
						|
			if (drawMsgText) {
 | 
						|
				if (drawMsgText->getMedia() && drawMsgText->getMedia()->hasReplyPreview()) {
 | 
						|
					ImagePtr replyPreview = drawMsgText->getMedia()->replyPreview();
 | 
						|
					if (!replyPreview->isNull()) {
 | 
						|
						QRect to(replyLeft, backy + st::msgReplyPadding.top(), st::msgReplyBarSize.height(), st::msgReplyBarSize.height());
 | 
						|
						p.drawPixmap(to.x(), to.y(), replyPreview->pixSingle(ImageRoundRadius::Small, replyPreview->width() / cIntRetinaFactor(), replyPreview->height() / cIntRetinaFactor(), to.width(), to.height()));
 | 
						|
					}
 | 
						|
					replyLeft += st::msgReplyBarSize.height() + st::msgReplyBarSkip - st::msgReplyBarSize.width() - st::msgReplyBarPos.x();
 | 
						|
				}
 | 
						|
				p.setPen(st::replyColor);
 | 
						|
				if (_editMsgId) {
 | 
						|
					paintEditHeader(p, rect, replyLeft, backy);
 | 
						|
				} else {
 | 
						|
					_replyToName.drawElided(p, replyLeft, backy + st::msgReplyPadding.top(), width() - replyLeft - _fieldBarCancel.width() - st::msgReplyPadding.right());
 | 
						|
				}
 | 
						|
				p.setPen((((drawMsgText->toHistoryMessage() && drawMsgText->toHistoryMessage()->emptyText()) || drawMsgText->serviceMsg()) ? st::msgInDateFg : st::msgColor)->p);
 | 
						|
				_replyEditMsgText.drawElided(p, replyLeft, backy + st::msgReplyPadding.top() + st::msgServiceNameFont->height, width() - replyLeft - _fieldBarCancel.width() - st::msgReplyPadding.right());
 | 
						|
			} else {
 | 
						|
				p.setFont(st::msgDateFont->f);
 | 
						|
				p.setPen(st::msgInDateFg->p);
 | 
						|
				p.drawText(replyLeft, backy + st::msgReplyPadding.top() + (st::msgReplyBarSize.height() - st::msgDateFont->height) / 2 + st::msgDateFont->ascent, st::msgDateFont->elided(lang(lng_profile_loading), width() - replyLeft - _fieldBarCancel.width() - st::msgReplyPadding.right()));
 | 
						|
			}
 | 
						|
		}
 | 
						|
	} else if (from && text) {
 | 
						|
		int32 forwardLeft = st::replySkip;
 | 
						|
		p.drawSprite(QPoint(st::replyIconPos.x(), backy + st::replyIconPos.y()), st::forwardIcon);
 | 
						|
		if (!drawPreview) {
 | 
						|
			if (!preview->isNull()) {
 | 
						|
				QRect to(forwardLeft, backy + st::msgReplyPadding.top(), st::msgReplyBarSize.height(), st::msgReplyBarSize.height());
 | 
						|
				if (preview->width() == preview->height()) {
 | 
						|
					p.drawPixmap(to.x(), to.y(), preview->pix());
 | 
						|
				} else {
 | 
						|
					QRect from = (preview->width() > preview->height()) ? QRect((preview->width() - preview->height()) / 2, 0, preview->height(), preview->height()) : QRect(0, (preview->height() - preview->width()) / 2, preview->width(), preview->width());
 | 
						|
					p.drawPixmap(to, preview->pix(), from);
 | 
						|
				}
 | 
						|
				forwardLeft += st::msgReplyBarSize.height() + st::msgReplyBarSkip - st::msgReplyBarSize.width() - st::msgReplyBarPos.x();
 | 
						|
			}
 | 
						|
			p.setPen(st::replyColor->p);
 | 
						|
			from->drawElided(p, forwardLeft, backy + st::msgReplyPadding.top(), width() - forwardLeft - _fieldBarCancel.width() - st::msgReplyPadding.right());
 | 
						|
			p.setPen((serviceColor ? st::msgInDateFg : st::msgColor)->p);
 | 
						|
			text->drawElided(p, forwardLeft, backy + st::msgReplyPadding.top() + st::msgServiceNameFont->height, width() - forwardLeft - _fieldBarCancel.width() - st::msgReplyPadding.right());
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if (drawPreview) {
 | 
						|
		int32 previewLeft = st::replySkip + st::webPageLeft;
 | 
						|
		p.fillRect(st::replySkip, backy + st::msgReplyPadding.top(), st::webPageBar, st::msgReplyBarSize.height(), st::msgInReplyBarColor->b);
 | 
						|
		if ((_previewData->photo && !_previewData->photo->thumb->isNull()) || (_previewData->document && !_previewData->document->thumb->isNull())) {
 | 
						|
			ImagePtr replyPreview = _previewData->photo ? _previewData->photo->makeReplyPreview() : _previewData->document->makeReplyPreview();
 | 
						|
			if (!replyPreview->isNull()) {
 | 
						|
				QRect to(previewLeft, backy + st::msgReplyPadding.top(), st::msgReplyBarSize.height(), st::msgReplyBarSize.height());
 | 
						|
				if (replyPreview->width() == replyPreview->height()) {
 | 
						|
					p.drawPixmap(to.x(), to.y(), replyPreview->pix());
 | 
						|
				} else {
 | 
						|
					QRect from = (replyPreview->width() > replyPreview->height()) ? QRect((replyPreview->width() - replyPreview->height()) / 2, 0, replyPreview->height(), replyPreview->height()) : QRect(0, (replyPreview->height() - replyPreview->width()) / 2, replyPreview->width(), replyPreview->width());
 | 
						|
					p.drawPixmap(to, replyPreview->pix(), from);
 | 
						|
				}
 | 
						|
			}
 | 
						|
			previewLeft += st::msgReplyBarSize.height() + st::msgReplyBarSkip - st::msgReplyBarSize.width() - st::msgReplyBarPos.x();
 | 
						|
		}
 | 
						|
		p.setPen(st::replyColor->p);
 | 
						|
		_previewTitle.drawElided(p, previewLeft, backy + st::msgReplyPadding.top(), width() - previewLeft - _fieldBarCancel.width() - st::msgReplyPadding.right());
 | 
						|
		p.setPen(st::msgColor->p);
 | 
						|
		_previewDescription.drawElided(p, previewLeft, backy + st::msgReplyPadding.top() + st::msgServiceNameFont->height, width() - previewLeft - _fieldBarCancel.width() - st::msgReplyPadding.right());
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
namespace {
 | 
						|
 | 
						|
constexpr int DisplayEditTimeWarningMs = 300 * 1000;
 | 
						|
constexpr int FullDayInMs = 86400 * 1000;
 | 
						|
 | 
						|
} // namespace
 | 
						|
 | 
						|
void HistoryWidget::paintEditHeader(Painter &p, const QRect &rect, int left, int top) const {
 | 
						|
	if (!rect.intersects(QRect(left, top, width() - left, st::normalFont->height))) {
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	p.setFont(st::msgServiceNameFont);
 | 
						|
	p.drawText(left, top + st::msgReplyPadding.top() + st::msgServiceNameFont->ascent, lang(lng_edit_message));
 | 
						|
 | 
						|
	if (!_replyEditMsg) return;
 | 
						|
 | 
						|
	QString editTimeLeftText;
 | 
						|
	int updateIn = -1;
 | 
						|
	auto tmp = ::date(unixtime());
 | 
						|
	auto timeSinceMessage = _replyEditMsg->date.msecsTo(QDateTime::currentDateTime());
 | 
						|
	auto editTimeLeft = (Global::EditTimeLimit() * 1000LL) - timeSinceMessage;
 | 
						|
	if (editTimeLeft < 2) {
 | 
						|
		editTimeLeftText = qsl("0:00");
 | 
						|
	} else if (editTimeLeft > DisplayEditTimeWarningMs) {
 | 
						|
		updateIn = static_cast<int>(qMin(editTimeLeft - DisplayEditTimeWarningMs, qint64(FullDayInMs)));
 | 
						|
	} else {
 | 
						|
		updateIn = static_cast<int>(editTimeLeft % 1000);
 | 
						|
		if (!updateIn) {
 | 
						|
			updateIn = 1000;
 | 
						|
		}
 | 
						|
		++updateIn;
 | 
						|
 | 
						|
		editTimeLeft = (editTimeLeft - 1) / 1000; // seconds
 | 
						|
		editTimeLeftText = qsl("%1:%2").arg(editTimeLeft / 60).arg(editTimeLeft % 60, 2, 10, QChar('0'));
 | 
						|
	}
 | 
						|
 | 
						|
	// Restart timer only if we are sure that we've painted the whole timer.
 | 
						|
	if (rect.contains(QRect(left, top, width() - left, st::normalFont->height)) && updateIn > 0) {
 | 
						|
		_updateEditTimeLeftDisplay.start(updateIn);
 | 
						|
	}
 | 
						|
 | 
						|
	if (!editTimeLeftText.isEmpty()) {
 | 
						|
		p.setFont(st::normalFont);
 | 
						|
		p.setPen(st::msgInDateFg);
 | 
						|
		p.drawText(left + st::msgServiceNameFont->width(lang(lng_edit_message)) + st::normalFont->spacew, top + st::msgReplyPadding.top() + st::msgServiceNameFont->ascent, editTimeLeftText);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::drawRecordButton(Painter &p) {
 | 
						|
	if (a_recordDown.current() < 1) {
 | 
						|
		p.setOpacity(st::btnAttachEmoji.opacity * (1 - a_recordOver.current()) + st::btnAttachEmoji.overOpacity * a_recordOver.current());
 | 
						|
		p.drawSprite(_send.x() + (_send.width() - st::btnRecordAudio.pxWidth()) / 2, _send.y() + (_send.height() - st::btnRecordAudio.pxHeight()) / 2, st::btnRecordAudio);
 | 
						|
	}
 | 
						|
	if (a_recordDown.current() > 0) {
 | 
						|
		p.setOpacity(a_recordDown.current());
 | 
						|
		p.drawSprite(_send.x() + (_send.width() - st::btnRecordAudioActive.pxWidth()) / 2, _send.y() + (_send.height() - st::btnRecordAudioActive.pxHeight()) / 2, st::btnRecordAudioActive);
 | 
						|
	}
 | 
						|
	p.setOpacity(1);
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::drawRecording(Painter &p) {
 | 
						|
	p.setPen(Qt::NoPen);
 | 
						|
	p.setBrush(st::recordSignalColor->b);
 | 
						|
	p.setRenderHint(QPainter::HighQualityAntialiasing);
 | 
						|
	float64 delta = qMin(float64(a_recordingLevel.current()) / 0x4000, 1.);
 | 
						|
	int32 d = 2 * qRound(st::recordSignalMin + (delta * (st::recordSignalMax - st::recordSignalMin)));
 | 
						|
	p.drawEllipse(_attachPhoto.x() + (_attachEmoji.width() - d) / 2, _attachPhoto.y() + (_attachPhoto.height() - d) / 2, d, d);
 | 
						|
	p.setRenderHint(QPainter::HighQualityAntialiasing, false);
 | 
						|
 | 
						|
	QString duration = formatDurationText(_recordingSamples / AudioVoiceMsgFrequency);
 | 
						|
	p.setFont(st::recordFont->f);
 | 
						|
 | 
						|
	p.setPen(st::black->p);
 | 
						|
	p.drawText(_attachPhoto.x() + _attachEmoji.width(), _attachPhoto.y() + st::recordTextTop + st::recordFont->ascent, duration);
 | 
						|
 | 
						|
	int32 left = _attachPhoto.x() + _attachEmoji.width() + st::recordFont->width(duration) + ((_send.width() - st::btnRecordAudio.pxWidth()) / 2);
 | 
						|
	int32 right = width() - _send.width();
 | 
						|
 | 
						|
	p.setPen(a_recordCancel.current());
 | 
						|
	p.drawText(left + (right - left - _recordCancelWidth) / 2, _attachPhoto.y() + st::recordTextTop + st::recordFont->ascent, lang(lng_record_cancel));
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::drawPinnedBar(Painter &p) {
 | 
						|
	t_assert(_pinnedBar != nullptr);
 | 
						|
 | 
						|
	Text *from = 0, *text = 0;
 | 
						|
	bool serviceColor = false, hasForward = readyToForward();
 | 
						|
	ImagePtr preview;
 | 
						|
	p.fillRect(0, 0, width(), st::replyHeight, st::taMsgField.bgColor);
 | 
						|
 | 
						|
	QRect rbar(rtlrect(st::msgReplyBarSkip + st::msgReplyBarPos.x(), st::msgReplyPadding.top() + st::msgReplyBarPos.y(), st::msgReplyBarSize.width(), st::msgReplyBarSize.height(), width()));
 | 
						|
	p.fillRect(rbar, st::msgInReplyBarColor);
 | 
						|
 | 
						|
	int32 left = st::msgReplyBarSkip + st::msgReplyBarSkip;
 | 
						|
	if (_pinnedBar->msg) {
 | 
						|
		if (_pinnedBar->msg->getMedia() && _pinnedBar->msg->getMedia()->hasReplyPreview()) {
 | 
						|
			ImagePtr replyPreview = _pinnedBar->msg->getMedia()->replyPreview();
 | 
						|
			if (!replyPreview->isNull()) {
 | 
						|
				QRect to(left, st::msgReplyPadding.top(), st::msgReplyBarSize.height(), st::msgReplyBarSize.height());
 | 
						|
				p.drawPixmap(to.x(), to.y(), replyPreview->pixSingle(ImageRoundRadius::Small, replyPreview->width() / cIntRetinaFactor(), replyPreview->height() / cIntRetinaFactor(), to.width(), to.height()));
 | 
						|
			}
 | 
						|
			left += st::msgReplyBarSize.height() + st::msgReplyBarSkip - st::msgReplyBarSize.width() - st::msgReplyBarPos.x();
 | 
						|
		}
 | 
						|
		p.setPen(st::replyColor);
 | 
						|
		p.setFont(st::msgServiceNameFont);
 | 
						|
		p.drawText(left, st::msgReplyPadding.top() + st::msgServiceNameFont->ascent, lang(lng_pinned_message));
 | 
						|
 | 
						|
		p.setPen((((_pinnedBar->msg->toHistoryMessage() && _pinnedBar->msg->toHistoryMessage()->emptyText()) || _pinnedBar->msg->serviceMsg()) ? st::msgInDateFg : st::msgColor)->p);
 | 
						|
		_pinnedBar->text.drawElided(p, left, st::msgReplyPadding.top() + st::msgServiceNameFont->height, width() - left - _pinnedBar->cancel->width() - st::msgReplyPadding.right());
 | 
						|
	} else {
 | 
						|
		p.setFont(st::msgDateFont);
 | 
						|
		p.setPen(st::msgInDateFg);
 | 
						|
		p.drawText(left, st::msgReplyPadding.top() + (st::msgReplyBarSize.height() - st::msgDateFont->height) / 2 + st::msgDateFont->ascent, st::msgDateFont->elided(lang(lng_profile_loading), width() - left - _pinnedBar->cancel->width() - st::msgReplyPadding.right()));
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::paintEvent(QPaintEvent *e) {
 | 
						|
	if (!App::main() || (App::wnd() && App::wnd()->contentOverlapped(this, e))) {
 | 
						|
		return;
 | 
						|
	}
 | 
						|
	if (hasPendingResizedItems()) {
 | 
						|
		updateListSize();
 | 
						|
	}
 | 
						|
 | 
						|
	Painter p(this);
 | 
						|
	QRect r(e->rect());
 | 
						|
	if (r != rect()) {
 | 
						|
		p.setClipRect(r);
 | 
						|
	}
 | 
						|
	bool hasTopBar = !App::main()->topBar()->isHidden();
 | 
						|
 | 
						|
	if (_a_show.animating()) {
 | 
						|
		int retina = cIntRetinaFactor();
 | 
						|
		int inCacheTop = hasTopBar ? st::topBarHeight : 0;
 | 
						|
		if (a_coordOver.current() > 0) {
 | 
						|
			p.drawPixmap(QRect(0, 0, a_coordOver.current(), height()), _cacheUnder, QRect(-a_coordUnder.current() * retina, inCacheTop * retina, a_coordOver.current() * retina, height() * retina));
 | 
						|
			p.setOpacity(a_progress.current() * st::slideFadeOut);
 | 
						|
			p.fillRect(0, 0, a_coordOver.current(), height(), st::black);
 | 
						|
			p.setOpacity(1);
 | 
						|
		}
 | 
						|
		p.drawPixmap(QRect(a_coordOver.current(), 0, _cacheOver.width() / retina, height()), _cacheOver, QRect(0, inCacheTop * retina, _cacheOver.width(), height() * retina));
 | 
						|
		p.setOpacity(a_progress.current());
 | 
						|
		p.drawPixmap(QRect(a_coordOver.current() - st::slideShadow.pxWidth(), 0, st::slideShadow.pxWidth(), height()), App::sprite(), st::slideShadow.rect());
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	QRect fill(0, 0, width(), App::main()->height());
 | 
						|
	int fromy = App::main()->backgroundFromY(), x = 0, y = 0;
 | 
						|
	QPixmap cached = App::main()->cachedBackground(fill, x, y);
 | 
						|
	if (cached.isNull()) {
 | 
						|
		auto &pix = Window::chatBackground()->image();
 | 
						|
		if (Window::chatBackground()->tile()) {
 | 
						|
			int left = r.left(), top = r.top(), right = r.left() + r.width(), bottom = r.top() + r.height();
 | 
						|
			float64 w = pix.width() / cRetinaFactor(), h = pix.height() / cRetinaFactor();
 | 
						|
			int sx = qFloor(left / w), sy = qFloor((top - fromy) / h), cx = qCeil(right / w), cy = qCeil((bottom - fromy) / h);
 | 
						|
			for (int i = sx; i < cx; ++i) {
 | 
						|
				for (int j = sy; j < cy; ++j) {
 | 
						|
					p.drawPixmap(QPointF(i * w, fromy + j * h), pix);
 | 
						|
				}
 | 
						|
			}
 | 
						|
		} else {
 | 
						|
			bool smooth = p.renderHints().testFlag(QPainter::SmoothPixmapTransform);
 | 
						|
			p.setRenderHint(QPainter::SmoothPixmapTransform);
 | 
						|
 | 
						|
			QRect to, from;
 | 
						|
			App::main()->backgroundParams(fill, to, from);
 | 
						|
			to.moveTop(to.top() + fromy);
 | 
						|
			p.drawPixmap(to, pix, from);
 | 
						|
 | 
						|
			if (!smooth) p.setRenderHint(QPainter::SmoothPixmapTransform, false);
 | 
						|
		}
 | 
						|
	} else {
 | 
						|
		p.drawPixmap(x, fromy + y, cached);
 | 
						|
	}
 | 
						|
 | 
						|
	if (_list) {
 | 
						|
		if (!_field.isHidden() || _recording) {
 | 
						|
			drawField(p, r);
 | 
						|
			if (_send.isHidden()) {
 | 
						|
				drawRecordButton(p);
 | 
						|
				if (_recording) drawRecording(p);
 | 
						|
			}
 | 
						|
		}
 | 
						|
		if (_pinnedBar && !_pinnedBar->cancel->isHidden()) {
 | 
						|
			drawPinnedBar(p);
 | 
						|
		}
 | 
						|
		if (_scroll.isHidden()) {
 | 
						|
			p.setClipRect(_scroll.geometry());
 | 
						|
			HistoryLayout::paintEmpty(p, width(), height() - _field.height() - 2 * st::sendPadding);
 | 
						|
		}
 | 
						|
	} else {
 | 
						|
		style::font font(st::msgServiceFont);
 | 
						|
		int32 w = font->width(lang(lng_willbe_history)) + st::msgPadding.left() + st::msgPadding.right(), h = font->height + st::msgServicePadding.top() + st::msgServicePadding.bottom() + 2;
 | 
						|
		QRect tr((width() - w) / 2, (height() - _field.height() - 2 * st::sendPadding - h) / 2, w, h);
 | 
						|
		HistoryLayout::ServiceMessagePainter::paintBubble(p, tr.x(), tr.y(), tr.width(), tr.height());
 | 
						|
 | 
						|
		p.setPen(st::msgServiceColor->p);
 | 
						|
		p.setFont(font->f);
 | 
						|
		p.drawText(tr.left() + st::msgPadding.left(), tr.top() + st::msgServicePadding.top() + 1 + font->ascent, lang(lng_willbe_history));
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
QRect HistoryWidget::historyRect() const {
 | 
						|
	return _scroll.geometry();
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::destroyData() {
 | 
						|
	showHistory(0, 0);
 | 
						|
}
 | 
						|
 | 
						|
QStringList HistoryWidget::getMediasFromMime(const QMimeData *d) {
 | 
						|
	QString uriListFormat(qsl("text/uri-list"));
 | 
						|
	QStringList photoExtensions(cPhotoExtensions()), files;
 | 
						|
	if (!d->hasFormat(uriListFormat)) return QStringList();
 | 
						|
 | 
						|
	const QList<QUrl> &urls(d->urls());
 | 
						|
	if (urls.isEmpty()) return QStringList();
 | 
						|
 | 
						|
	files.reserve(urls.size());
 | 
						|
	for (QList<QUrl>::const_iterator i = urls.cbegin(), en = urls.cend(); i != en; ++i) {
 | 
						|
		if (!i->isLocalFile()) return QStringList();
 | 
						|
 | 
						|
		auto file = psConvertFileUrl(*i);
 | 
						|
 | 
						|
		QFileInfo info(file);
 | 
						|
		uint64 s = info.size();
 | 
						|
		if (s >= MaxUploadDocumentSize) {
 | 
						|
			if (s >= MaxUploadPhotoSize) {
 | 
						|
				continue;
 | 
						|
			} else {
 | 
						|
				bool foundGoodExtension = false;
 | 
						|
				for (QStringList::const_iterator j = photoExtensions.cbegin(), end = photoExtensions.cend(); j != end; ++j) {
 | 
						|
					if (file.right(j->size()).toLower() == (*j).toLower()) {
 | 
						|
						foundGoodExtension = true;
 | 
						|
					}
 | 
						|
				}
 | 
						|
				if (!foundGoodExtension) {
 | 
						|
					continue;
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
		files.push_back(file);
 | 
						|
	}
 | 
						|
	return files;
 | 
						|
}
 | 
						|
 | 
						|
QPoint HistoryWidget::clampMousePosition(QPoint point) {
 | 
						|
	if (point.x() < 0) {
 | 
						|
		point.setX(0);
 | 
						|
	} else if (point.x() >= _scroll.width()) {
 | 
						|
		point.setX(_scroll.width() - 1);
 | 
						|
	}
 | 
						|
	if (point.y() < _scroll.scrollTop()) {
 | 
						|
		point.setY(_scroll.scrollTop());
 | 
						|
	} else if (point.y() >= _scroll.scrollTop() + _scroll.height()) {
 | 
						|
		point.setY(_scroll.scrollTop() + _scroll.height() - 1);
 | 
						|
	}
 | 
						|
	return point;
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::onScrollTimer() {
 | 
						|
	int32 d = (_scrollDelta > 0) ? qMin(_scrollDelta * 3 / 20 + 1, int32(MaxScrollSpeed)) : qMax(_scrollDelta * 3 / 20 - 1, -int32(MaxScrollSpeed));
 | 
						|
	_scroll.scrollToY(_scroll.scrollTop() + d);
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::checkSelectingScroll(QPoint point) {
 | 
						|
	if (point.y() < _scroll.scrollTop()) {
 | 
						|
		_scrollDelta = point.y() - _scroll.scrollTop();
 | 
						|
	} else if (point.y() >= _scroll.scrollTop() + _scroll.height()) {
 | 
						|
		_scrollDelta = point.y() - _scroll.scrollTop() - _scroll.height() + 1;
 | 
						|
	} else {
 | 
						|
		_scrollDelta = 0;
 | 
						|
	}
 | 
						|
	if (_scrollDelta) {
 | 
						|
		_scrollTimer.start(15);
 | 
						|
	} else {
 | 
						|
		_scrollTimer.stop();
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryWidget::noSelectingScroll() {
 | 
						|
	_scrollTimer.stop();
 | 
						|
}
 | 
						|
 | 
						|
bool HistoryWidget::touchScroll(const QPoint &delta) {
 | 
						|
	int32 scTop = _scroll.scrollTop(), scMax = _scroll.scrollTopMax(), scNew = snap(scTop - delta.y(), 0, scMax);
 | 
						|
	if (scNew == scTop) return false;
 | 
						|
 | 
						|
	_scroll.scrollToY(scNew);
 | 
						|
	return true;
 | 
						|
}
 | 
						|
 | 
						|
HistoryWidget::~HistoryWidget() {
 | 
						|
	delete base::take(_list);
 | 
						|
}
 |