696 lines
		
	
	
	
		
			24 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			696 lines
		
	
	
	
		
			24 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-2017 John Preston, https://desktop.telegram.org
 | 
						|
*/
 | 
						|
#include "history/history_service.h"
 | 
						|
 | 
						|
#include "lang/lang_keys.h"
 | 
						|
#include "mainwidget.h"
 | 
						|
#include "apiwrap.h"
 | 
						|
#include "history/history_service_layout.h"
 | 
						|
#include "history/history_media_types.h"
 | 
						|
#include "history/history_message.h"
 | 
						|
#include "auth_session.h"
 | 
						|
#include "window/notifications_manager.h"
 | 
						|
 | 
						|
namespace {
 | 
						|
 | 
						|
constexpr auto kPinnedMessageTextLimit = 16;
 | 
						|
 | 
						|
} // namespace
 | 
						|
 | 
						|
TextParseOptions _historySrvOptions = {
 | 
						|
	TextParseLinks | TextParseMentions | TextParseHashtags/* | TextParseMultiline*/ | TextParseRichText, // flags
 | 
						|
	0, // maxw
 | 
						|
	0, // maxh
 | 
						|
	Qt::LayoutDirectionAuto, // lang-dependent
 | 
						|
};
 | 
						|
 | 
						|
void HistoryService::setMessageByAction(const MTPmessageAction &action) {
 | 
						|
	auto prepareChatAddUserText = [this](const MTPDmessageActionChatAddUser &action) {
 | 
						|
		auto result = PreparedText {};
 | 
						|
		auto &users = action.vusers.v;
 | 
						|
		if (users.size() == 1) {
 | 
						|
			auto u = App::user(peerFromUser(users[0]));
 | 
						|
			if (u == _from) {
 | 
						|
				result.links.push_back(fromLink());
 | 
						|
				result.text = lng_action_user_joined(lt_from, fromLinkText());
 | 
						|
			} else {
 | 
						|
				result.links.push_back(fromLink());
 | 
						|
				result.links.push_back(peerOpenClickHandler(u));
 | 
						|
				result.text = lng_action_add_user(lt_from, fromLinkText(), lt_user, textcmdLink(2, u->name));
 | 
						|
			}
 | 
						|
		} else if (users.isEmpty()) {
 | 
						|
			result.links.push_back(fromLink());
 | 
						|
			result.text = lng_action_add_user(lt_from, fromLinkText(), lt_user, "somebody");
 | 
						|
		} else {
 | 
						|
			result.links.push_back(fromLink());
 | 
						|
			for (auto i = 0, l = users.size(); i != l; ++i) {
 | 
						|
				auto user = App::user(peerFromUser(users[i]));
 | 
						|
				result.links.push_back(peerOpenClickHandler(user));
 | 
						|
 | 
						|
				auto linkText = textcmdLink(i + 2, user->name);
 | 
						|
				if (i == 0) {
 | 
						|
					result.text = linkText;
 | 
						|
				} else if (i + 1 == l) {
 | 
						|
					result.text = lng_action_add_users_and_last(lt_accumulated, result.text, lt_user, linkText);
 | 
						|
				} else {
 | 
						|
					result.text = lng_action_add_users_and_one(lt_accumulated, result.text, lt_user, linkText);
 | 
						|
				}
 | 
						|
			}
 | 
						|
			result.text = lng_action_add_users_many(lt_from, fromLinkText(), lt_users, result.text);
 | 
						|
		}
 | 
						|
		return result;
 | 
						|
	};
 | 
						|
 | 
						|
	auto prepareChatJoinedByLink = [this](const MTPDmessageActionChatJoinedByLink &action) {
 | 
						|
		auto result = PreparedText {};
 | 
						|
		result.links.push_back(fromLink());
 | 
						|
		result.text = lng_action_user_joined_by_link(lt_from, fromLinkText());
 | 
						|
		return result;
 | 
						|
	};
 | 
						|
 | 
						|
	auto prepareChatCreate = [this](const MTPDmessageActionChatCreate &action) {
 | 
						|
		auto result = PreparedText {};
 | 
						|
		result.links.push_back(fromLink());
 | 
						|
		result.text = lng_action_created_chat(lt_from, fromLinkText(), lt_title, textClean(qs(action.vtitle)));
 | 
						|
		return result;
 | 
						|
	};
 | 
						|
 | 
						|
	auto prepareChannelCreate = [this](const MTPDmessageActionChannelCreate &action) {
 | 
						|
		auto result = PreparedText {};
 | 
						|
		if (isPost()) {
 | 
						|
			result.text = lang(lng_action_created_channel);
 | 
						|
		} else {
 | 
						|
			result.links.push_back(fromLink());
 | 
						|
			result.text = lng_action_created_chat(lt_from, fromLinkText(), lt_title, textClean(qs(action.vtitle)));
 | 
						|
		}
 | 
						|
		return result;
 | 
						|
	};
 | 
						|
 | 
						|
	auto prepareChatDeletePhoto = [this] {
 | 
						|
		auto result = PreparedText {};
 | 
						|
		if (isPost()) {
 | 
						|
			result.text = lang(lng_action_removed_photo_channel);
 | 
						|
		} else {
 | 
						|
			result.links.push_back(fromLink());
 | 
						|
			result.text = lng_action_removed_photo(lt_from, fromLinkText());
 | 
						|
		}
 | 
						|
		return result;
 | 
						|
	};
 | 
						|
 | 
						|
	auto prepareChatDeleteUser = [this](const MTPDmessageActionChatDeleteUser &action) {
 | 
						|
		auto result = PreparedText {};
 | 
						|
		if (peerFromUser(action.vuser_id) == _from->id) {
 | 
						|
			result.links.push_back(fromLink());
 | 
						|
			result.text = lng_action_user_left(lt_from, fromLinkText());
 | 
						|
		} else {
 | 
						|
			auto user = App::user(peerFromUser(action.vuser_id));
 | 
						|
			result.links.push_back(fromLink());
 | 
						|
			result.links.push_back(peerOpenClickHandler(user));
 | 
						|
			result.text = lng_action_kick_user(lt_from, fromLinkText(), lt_user, textcmdLink(2, user->name));
 | 
						|
		}
 | 
						|
		return result;
 | 
						|
	};
 | 
						|
 | 
						|
	auto prepareChatEditPhoto = [this](const MTPDmessageActionChatEditPhoto &action) {
 | 
						|
		auto result = PreparedText {};
 | 
						|
		if (isPost()) {
 | 
						|
			result.text = lang(lng_action_changed_photo_channel);
 | 
						|
		} else {
 | 
						|
			result.links.push_back(fromLink());
 | 
						|
			result.text = lng_action_changed_photo(lt_from, fromLinkText());
 | 
						|
		}
 | 
						|
		return result;
 | 
						|
	};
 | 
						|
 | 
						|
	auto prepareChatEditTitle = [this](const MTPDmessageActionChatEditTitle &action) {
 | 
						|
		auto result = PreparedText {};
 | 
						|
		if (isPost()) {
 | 
						|
			result.text = lng_action_changed_title_channel(lt_title, textClean(qs(action.vtitle)));
 | 
						|
		} else {
 | 
						|
			result.links.push_back(fromLink());
 | 
						|
			result.text = lng_action_changed_title(lt_from, fromLinkText(), lt_title, textClean(qs(action.vtitle)));
 | 
						|
		}
 | 
						|
		return result;
 | 
						|
	};
 | 
						|
 | 
						|
	auto messageText = PreparedText {};
 | 
						|
 | 
						|
	switch (action.type()) {
 | 
						|
	case mtpc_messageActionChatAddUser: messageText = prepareChatAddUserText(action.c_messageActionChatAddUser()); break;
 | 
						|
	case mtpc_messageActionChatJoinedByLink: messageText = prepareChatJoinedByLink(action.c_messageActionChatJoinedByLink()); break;
 | 
						|
	case mtpc_messageActionChatCreate: messageText = prepareChatCreate(action.c_messageActionChatCreate()); break;
 | 
						|
	case mtpc_messageActionChannelCreate: messageText = prepareChannelCreate(action.c_messageActionChannelCreate()); break;
 | 
						|
	case mtpc_messageActionHistoryClear: break; // Leave empty text.
 | 
						|
	case mtpc_messageActionChatDeletePhoto: messageText = prepareChatDeletePhoto(); break;
 | 
						|
	case mtpc_messageActionChatDeleteUser: messageText = prepareChatDeleteUser(action.c_messageActionChatDeleteUser()); break;
 | 
						|
	case mtpc_messageActionChatEditPhoto: messageText = prepareChatEditPhoto(action.c_messageActionChatEditPhoto()); break;
 | 
						|
	case mtpc_messageActionChatEditTitle: messageText = prepareChatEditTitle(action.c_messageActionChatEditTitle()); break;
 | 
						|
	case mtpc_messageActionChatMigrateTo: messageText.text = lang(lng_action_group_migrate); break;
 | 
						|
	case mtpc_messageActionChannelMigrateFrom: messageText.text = lang(lng_action_group_migrate); break;
 | 
						|
	case mtpc_messageActionPinMessage: messageText = preparePinnedText(); break;
 | 
						|
	case mtpc_messageActionGameScore: messageText = prepareGameScoreText(); break;
 | 
						|
	case mtpc_messageActionPhoneCall: Unexpected("PhoneCall type in HistoryService.");
 | 
						|
	case mtpc_messageActionPaymentSent: messageText = preparePaymentSentText(); break;
 | 
						|
	default: messageText.text = lang(lng_message_empty); break;
 | 
						|
	}
 | 
						|
 | 
						|
	setServiceText(messageText);
 | 
						|
 | 
						|
	// Additional information.
 | 
						|
	switch (action.type()) {
 | 
						|
	case mtpc_messageActionChatAddUser: {
 | 
						|
		if (auto channel = history()->peer->asMegagroup()) {
 | 
						|
			auto &users = action.c_messageActionChatAddUser().vusers;
 | 
						|
			for_const (auto &item, users.v) {
 | 
						|
				if (item.v == AuthSession::CurrentUserId()) {
 | 
						|
					channel->mgInfo->joinedMessageFound = true;
 | 
						|
					break;
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	} break;
 | 
						|
 | 
						|
	case mtpc_messageActionChatJoinedByLink: {
 | 
						|
		if (_from->isSelf() && history()->peer->isMegagroup()) {
 | 
						|
			history()->peer->asChannel()->mgInfo->joinedMessageFound = true;
 | 
						|
		}
 | 
						|
	} break;
 | 
						|
 | 
						|
	case mtpc_messageActionChatEditPhoto: {
 | 
						|
		auto &photo = action.c_messageActionChatEditPhoto().vphoto;
 | 
						|
		if (photo.type() == mtpc_photo) {
 | 
						|
			_media = std::make_unique<HistoryPhoto>(this, history()->peer, photo.c_photo(), st::msgServicePhotoWidth);
 | 
						|
		}
 | 
						|
	} break;
 | 
						|
 | 
						|
	case mtpc_messageActionChatMigrateTo:
 | 
						|
	case mtpc_messageActionChannelMigrateFrom: {
 | 
						|
		_flags |= MTPDmessage_ClientFlag::f_is_group_migrate;
 | 
						|
	} break;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
bool HistoryService::updateDependent(bool force) {
 | 
						|
	auto dependent = GetDependentData();
 | 
						|
	t_assert(dependent != nullptr);
 | 
						|
 | 
						|
	if (!force) {
 | 
						|
		if (!dependent->msgId || dependent->msg) {
 | 
						|
			return true;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if (!dependent->lnk) {
 | 
						|
		dependent->lnk = goToMessageClickHandler(history()->peer, dependent->msgId);
 | 
						|
	}
 | 
						|
	bool gotDependencyItem = false;
 | 
						|
	if (!dependent->msg) {
 | 
						|
		dependent->msg = App::histItemById(channelId(), dependent->msgId);
 | 
						|
		if (dependent->msg) {
 | 
						|
			App::historyRegDependency(this, dependent->msg);
 | 
						|
			gotDependencyItem = true;
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if (dependent->msg) {
 | 
						|
		updateDependentText();
 | 
						|
	} else if (force) {
 | 
						|
		if (dependent->msgId > 0) {
 | 
						|
			dependent->msgId = 0;
 | 
						|
			gotDependencyItem = true;
 | 
						|
		}
 | 
						|
		updateDependentText();
 | 
						|
	}
 | 
						|
	if (force && gotDependencyItem) {
 | 
						|
		AuthSession::Current().notifications().checkDelayed();
 | 
						|
	}
 | 
						|
	return (dependent->msg || !dependent->msgId);
 | 
						|
}
 | 
						|
 | 
						|
HistoryService::PreparedText HistoryService::preparePinnedText() {
 | 
						|
	auto result = PreparedText {};
 | 
						|
	auto pinned = Get<HistoryServicePinned>();
 | 
						|
	if (pinned && pinned->msg) {
 | 
						|
		auto mediaText = ([pinned]() -> QString {
 | 
						|
			auto media = pinned->msg->getMedia();
 | 
						|
			switch (media ? media->type() : MediaTypeCount) {
 | 
						|
			case MediaTypePhoto: return lang(lng_action_pinned_media_photo);
 | 
						|
			case MediaTypeVideo: return lang(lng_action_pinned_media_video);
 | 
						|
			case MediaTypeContact: return lang(lng_action_pinned_media_contact);
 | 
						|
			case MediaTypeFile: return lang(lng_action_pinned_media_file);
 | 
						|
			case MediaTypeGif: {
 | 
						|
				if (auto document = media->getDocument()) {
 | 
						|
					if (document->isRoundVideo()) {
 | 
						|
						return lang(lng_action_pinned_media_video_message);
 | 
						|
					}
 | 
						|
				}
 | 
						|
				return lang(lng_action_pinned_media_gif);
 | 
						|
			} break;
 | 
						|
			case MediaTypeSticker: {
 | 
						|
				auto emoji = static_cast<HistorySticker*>(media)->emoji();
 | 
						|
				if (emoji.isEmpty()) {
 | 
						|
					return lang(lng_action_pinned_media_sticker);
 | 
						|
				}
 | 
						|
				return lng_action_pinned_media_emoji_sticker(lt_emoji, emoji);
 | 
						|
			} break;
 | 
						|
			case MediaTypeLocation: return lang(lng_action_pinned_media_location);
 | 
						|
			case MediaTypeMusicFile: return lang(lng_action_pinned_media_audio);
 | 
						|
			case MediaTypeVoiceFile: return lang(lng_action_pinned_media_voice);
 | 
						|
			case MediaTypeGame: {
 | 
						|
				auto title = static_cast<HistoryGame*>(media)->game()->title;
 | 
						|
				return lng_action_pinned_media_game(lt_game, title);
 | 
						|
			} break;
 | 
						|
			}
 | 
						|
			return QString();
 | 
						|
		})();
 | 
						|
 | 
						|
		result.links.push_back(fromLink());
 | 
						|
		result.links.push_back(pinned->lnk);
 | 
						|
		if (mediaText.isEmpty()) {
 | 
						|
			auto original = pinned->msg->originalText().text;
 | 
						|
			auto cutAt = 0;
 | 
						|
			auto limit = kPinnedMessageTextLimit;
 | 
						|
			auto size = original.size();
 | 
						|
			for (; limit != 0;) {
 | 
						|
				--limit;
 | 
						|
				if (cutAt >= size) break;
 | 
						|
				if (original.at(cutAt).isLowSurrogate() && cutAt + 1 < size && original.at(cutAt + 1).isHighSurrogate()) {
 | 
						|
					cutAt += 2;
 | 
						|
				} else {
 | 
						|
					++cutAt;
 | 
						|
				}
 | 
						|
			}
 | 
						|
			if (!limit && cutAt + 5 < size) {
 | 
						|
				original = original.mid(0, cutAt) + qstr("...");
 | 
						|
			}
 | 
						|
			result.text = lng_action_pinned_message(lt_from, fromLinkText(), lt_text, textcmdLink(2, original));
 | 
						|
		} else {
 | 
						|
			result.text = lng_action_pinned_media(lt_from, fromLinkText(), lt_media, textcmdLink(2, mediaText));
 | 
						|
		}
 | 
						|
	} else if (pinned && pinned->msgId) {
 | 
						|
		result.links.push_back(fromLink());
 | 
						|
		result.links.push_back(pinned->lnk);
 | 
						|
		result.text = lng_action_pinned_media(lt_from, fromLinkText(), lt_media, textcmdLink(2, lang(lng_contacts_loading)));
 | 
						|
	} else {
 | 
						|
		result.links.push_back(fromLink());
 | 
						|
		result.text = lng_action_pinned_media(lt_from, fromLinkText(), lt_media, lang(lng_deleted_message));
 | 
						|
	}
 | 
						|
	return result;
 | 
						|
}
 | 
						|
 | 
						|
HistoryService::PreparedText HistoryService::prepareGameScoreText() {
 | 
						|
	auto result = PreparedText {};
 | 
						|
	auto gamescore = Get<HistoryServiceGameScore>();
 | 
						|
 | 
						|
	auto computeGameTitle = [gamescore, &result]() -> QString {
 | 
						|
		if (gamescore && gamescore->msg) {
 | 
						|
			if (auto media = gamescore->msg->getMedia()) {
 | 
						|
				if (media->type() == MediaTypeGame) {
 | 
						|
					result.links.push_back(MakeShared<ReplyMarkupClickHandler>(gamescore->msg, 0, 0));
 | 
						|
					auto titleText = static_cast<HistoryGame*>(media)->game()->title;
 | 
						|
					return textcmdLink(result.links.size(), titleText);
 | 
						|
				}
 | 
						|
			}
 | 
						|
			return lang(lng_deleted_message);
 | 
						|
		} else if (gamescore && gamescore->msgId) {
 | 
						|
			return lang(lng_contacts_loading);
 | 
						|
		}
 | 
						|
		return QString();
 | 
						|
	};
 | 
						|
 | 
						|
	auto scoreNumber = gamescore ? gamescore->score : 0;
 | 
						|
	if (_from->isSelf()) {
 | 
						|
		auto gameTitle = computeGameTitle();
 | 
						|
		if (gameTitle.isEmpty()) {
 | 
						|
			result.text = lng_action_game_you_scored_no_game(lt_count, scoreNumber);
 | 
						|
		} else {
 | 
						|
			result.text = lng_action_game_you_scored(lt_count, scoreNumber, lt_game, gameTitle);
 | 
						|
		}
 | 
						|
	} else {
 | 
						|
		result.links.push_back(fromLink());
 | 
						|
		auto gameTitle = computeGameTitle();
 | 
						|
		if (gameTitle.isEmpty()) {
 | 
						|
			result.text = lng_action_game_score_no_game(lt_count, scoreNumber, lt_from, fromLinkText());
 | 
						|
		} else {
 | 
						|
			result.text = lng_action_game_score(lt_count, scoreNumber, lt_from, fromLinkText(), lt_game, gameTitle);
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return result;
 | 
						|
}
 | 
						|
 | 
						|
HistoryService::PreparedText HistoryService::preparePaymentSentText() {
 | 
						|
	auto result = PreparedText {};
 | 
						|
	auto payment = Get<HistoryServicePayment>();
 | 
						|
 | 
						|
	auto invoiceTitle = ([payment]() -> QString {
 | 
						|
		if (payment && payment->msg) {
 | 
						|
			if (auto media = payment->msg->getMedia()) {
 | 
						|
				if (media->type() == MediaTypeInvoice) {
 | 
						|
					return static_cast<HistoryInvoice*>(media)->getTitle();
 | 
						|
				}
 | 
						|
			}
 | 
						|
			return lang(lng_deleted_message);
 | 
						|
		} else if (payment && payment->msgId) {
 | 
						|
			return lang(lng_contacts_loading);
 | 
						|
		}
 | 
						|
		return QString();
 | 
						|
	})();
 | 
						|
 | 
						|
	if (invoiceTitle.isEmpty()) {
 | 
						|
		result.text = lng_action_payment_done(lt_amount, payment->amount, lt_user, history()->peer->name);
 | 
						|
	} else {
 | 
						|
		result.text = lng_action_payment_done_for(lt_amount, payment->amount, lt_user, history()->peer->name, lt_invoice, invoiceTitle);
 | 
						|
	}
 | 
						|
	return result;
 | 
						|
}
 | 
						|
 | 
						|
HistoryService::HistoryService(gsl::not_null<History*> history, const MTPDmessageService &message) :
 | 
						|
	HistoryItem(history, message.vid.v, mtpCastFlags(message.vflags.v), ::date(message.vdate), message.has_from_id() ? message.vfrom_id.v : 0) {
 | 
						|
	createFromMtp(message);
 | 
						|
	setMessageByAction(message.vaction);
 | 
						|
}
 | 
						|
 | 
						|
HistoryService::HistoryService(gsl::not_null<History*> history, MsgId msgId, QDateTime date, const PreparedText &message, MTPDmessage::Flags flags, int32 from, PhotoData *photo) :
 | 
						|
	HistoryItem(history, msgId, flags, date, from) {
 | 
						|
	setServiceText(message);
 | 
						|
	if (photo) {
 | 
						|
		_media = std::make_unique<HistoryPhoto>(this, history->peer, photo, st::msgServicePhotoWidth);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryService::initDimensions() {
 | 
						|
	_maxw = _text.maxWidth() + st::msgServicePadding.left() + st::msgServicePadding.right();
 | 
						|
	_minh = _text.minHeight();
 | 
						|
	if (_media) {
 | 
						|
		_media->initDimensions();
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
bool HistoryService::updateDependencyItem() {
 | 
						|
	if (GetDependentData()) {
 | 
						|
		return updateDependent(true);
 | 
						|
	}
 | 
						|
	return HistoryItem::updateDependencyItem();
 | 
						|
}
 | 
						|
 | 
						|
QRect HistoryService::countGeometry() const {
 | 
						|
	auto result = QRect(0, 0, width(), _height);
 | 
						|
	if (Adaptive::ChatWide()) {
 | 
						|
		result.setWidth(qMin(result.width(), st::msgMaxWidth + 2 * st::msgPhotoSkip + 2 * st::msgMargin.left()));
 | 
						|
	}
 | 
						|
	return result.marginsRemoved(st::msgServiceMargin);
 | 
						|
}
 | 
						|
 | 
						|
TextWithEntities HistoryService::selectedText(TextSelection selection) const {
 | 
						|
	return _text.originalTextWithEntities((selection == FullSelection) ? AllTextSelection : selection);
 | 
						|
}
 | 
						|
 | 
						|
QString HistoryService::inDialogsText() const {
 | 
						|
	return textcmdLink(1, textClean(notificationText()));
 | 
						|
}
 | 
						|
 | 
						|
QString HistoryService::inReplyText() const {
 | 
						|
	QString result = HistoryService::notificationText();
 | 
						|
	return result.trimmed().startsWith(author()->name) ? result.trimmed().mid(author()->name.size()).trimmed() : result;
 | 
						|
}
 | 
						|
 | 
						|
void HistoryService::setServiceText(const PreparedText &prepared) {
 | 
						|
	_text.setText(st::serviceTextStyle, prepared.text, _historySrvOptions);
 | 
						|
	auto linkIndex = 0;
 | 
						|
	for_const (auto &link, prepared.links) {
 | 
						|
		// Link indices start with 1.
 | 
						|
		_text.setLink(++linkIndex, link);
 | 
						|
	}
 | 
						|
 | 
						|
	setPendingInitDimensions();
 | 
						|
	_textWidth = -1;
 | 
						|
	_textHeight = 0;
 | 
						|
}
 | 
						|
 | 
						|
void HistoryService::draw(Painter &p, QRect clip, TextSelection selection, TimeMs ms) const {
 | 
						|
	auto height = _height - st::msgServiceMargin.top() - st::msgServiceMargin.bottom();
 | 
						|
	auto dateh = 0;
 | 
						|
	auto unreadbarh = 0;
 | 
						|
	if (auto date = Get<HistoryMessageDate>()) {
 | 
						|
		dateh = date->height();
 | 
						|
		p.translate(0, dateh);
 | 
						|
		clip.translate(0, -dateh);
 | 
						|
		height -= dateh;
 | 
						|
	}
 | 
						|
	if (auto unreadbar = Get<HistoryMessageUnreadBar>()) {
 | 
						|
		unreadbarh = unreadbar->height();
 | 
						|
		if (clip.intersects(QRect(0, 0, width(), unreadbarh))) {
 | 
						|
			unreadbar->paint(p, 0, width());
 | 
						|
		}
 | 
						|
		p.translate(0, unreadbarh);
 | 
						|
		clip.translate(0, -unreadbarh);
 | 
						|
		height -= unreadbarh;
 | 
						|
	}
 | 
						|
 | 
						|
	HistoryLayout::PaintContext context(ms, clip, selection);
 | 
						|
	HistoryLayout::ServiceMessagePainter::paint(p, this, context, height);
 | 
						|
 | 
						|
	if (auto skiph = dateh + unreadbarh) {
 | 
						|
		p.translate(0, -skiph);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
int HistoryService::resizeContentGetHeight() {
 | 
						|
	_height = displayedDateHeight();
 | 
						|
	if (auto unreadbar = Get<HistoryMessageUnreadBar>()) {
 | 
						|
		_height += unreadbar->height();
 | 
						|
	}
 | 
						|
 | 
						|
	if (_text.isEmpty()) {
 | 
						|
		_textHeight = 0;
 | 
						|
	} else {
 | 
						|
		auto contentWidth = width();
 | 
						|
		if (Adaptive::ChatWide()) {
 | 
						|
			accumulate_min(contentWidth, st::msgMaxWidth + 2 * st::msgPhotoSkip + 2 * st::msgMargin.left());
 | 
						|
		}
 | 
						|
		contentWidth -= st::msgServiceMargin.left() + st::msgServiceMargin.left(); // two small margins
 | 
						|
		if (contentWidth < st::msgServicePadding.left() + st::msgServicePadding.right() + 1) {
 | 
						|
			contentWidth = st::msgServicePadding.left() + st::msgServicePadding.right() + 1;
 | 
						|
		}
 | 
						|
 | 
						|
		auto nwidth = qMax(contentWidth - st::msgServicePadding.left() - st::msgServicePadding.right(), 0);
 | 
						|
		if (nwidth != _textWidth) {
 | 
						|
			_textWidth = nwidth;
 | 
						|
			_textHeight = _text.countHeight(nwidth);
 | 
						|
		}
 | 
						|
		if (contentWidth >= _maxw) {
 | 
						|
			_height += _minh;
 | 
						|
		} else {
 | 
						|
			_height += _textHeight;
 | 
						|
		}
 | 
						|
		_height += st::msgServicePadding.top() + st::msgServicePadding.bottom() + st::msgServiceMargin.top() + st::msgServiceMargin.bottom();
 | 
						|
		if (_media) {
 | 
						|
			_height += st::msgServiceMargin.top() + _media->resizeGetHeight(_media->currentWidth());
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return _height;
 | 
						|
}
 | 
						|
 | 
						|
bool HistoryService::hasPoint(QPoint point) const {
 | 
						|
	auto g = countGeometry();
 | 
						|
	if (g.width() < 1) {
 | 
						|
		return false;
 | 
						|
	}
 | 
						|
 | 
						|
	if (auto dateh = displayedDateHeight()) {
 | 
						|
		g.setTop(g.top() + dateh);
 | 
						|
	}
 | 
						|
	if (auto unreadbar = Get<HistoryMessageUnreadBar>()) {
 | 
						|
		g.setTop(g.top() + unreadbar->height());
 | 
						|
	}
 | 
						|
	if (_media) {
 | 
						|
		g.setHeight(g.height() - (st::msgServiceMargin.top() + _media->height()));
 | 
						|
	}
 | 
						|
	return g.contains(point);
 | 
						|
}
 | 
						|
 | 
						|
HistoryTextState HistoryService::getState(QPoint point, HistoryStateRequest request) const {
 | 
						|
	HistoryTextState result;
 | 
						|
 | 
						|
	auto g = countGeometry();
 | 
						|
	if (g.width() < 1) {
 | 
						|
		return result;
 | 
						|
	}
 | 
						|
 | 
						|
	if (auto dateh = displayedDateHeight()) {
 | 
						|
		point.setY(point.y() - dateh);
 | 
						|
		g.setHeight(g.height() - dateh);
 | 
						|
	}
 | 
						|
	if (auto unreadbar = Get<HistoryMessageUnreadBar>()) {
 | 
						|
		auto unreadbarh = unreadbar->height();
 | 
						|
		point.setY(point.y() - unreadbarh);
 | 
						|
		g.setHeight(g.height() - unreadbarh);
 | 
						|
	}
 | 
						|
 | 
						|
	if (_media) {
 | 
						|
		g.setHeight(g.height() - (st::msgServiceMargin.top() + _media->height()));
 | 
						|
	}
 | 
						|
	auto trect = g.marginsAdded(-st::msgServicePadding);
 | 
						|
	if (trect.contains(point)) {
 | 
						|
		auto textRequest = request.forText();
 | 
						|
		textRequest.align = style::al_center;
 | 
						|
		result = _text.getState(point - trect.topLeft(), trect.width(), textRequest);
 | 
						|
		if (auto gamescore = Get<HistoryServiceGameScore>()) {
 | 
						|
			if (!result.link && result.cursor == HistoryInTextCursorState && g.contains(point)) {
 | 
						|
				result.link = gamescore->lnk;
 | 
						|
			}
 | 
						|
		} else if (auto payment = Get<HistoryServicePayment>()) {
 | 
						|
			if (!result.link && result.cursor == HistoryInTextCursorState && g.contains(point)) {
 | 
						|
				result.link = payment->lnk;
 | 
						|
			}
 | 
						|
		}
 | 
						|
	} else if (_media) {
 | 
						|
		result = _media->getState(point - QPoint(st::msgServiceMargin.left() + (g.width() - _media->maxWidth()) / 2, st::msgServiceMargin.top() + g.height() + st::msgServiceMargin.top()), request);
 | 
						|
	}
 | 
						|
	return result;
 | 
						|
}
 | 
						|
 | 
						|
void HistoryService::createFromMtp(const MTPDmessageService &message) {
 | 
						|
	if (message.vaction.type() == mtpc_messageActionGameScore) {
 | 
						|
		UpdateComponents(HistoryServiceGameScore::Bit());
 | 
						|
		Get<HistoryServiceGameScore>()->score = message.vaction.c_messageActionGameScore().vscore.v;
 | 
						|
	} else if (message.vaction.type() == mtpc_messageActionPaymentSent) {
 | 
						|
		UpdateComponents(HistoryServicePayment::Bit());
 | 
						|
		auto amount = message.vaction.c_messageActionPaymentSent().vtotal_amount.v;
 | 
						|
		auto currency = qs(message.vaction.c_messageActionPaymentSent().vcurrency);
 | 
						|
		Get<HistoryServicePayment>()->amount = HistoryInvoice::fillAmountAndCurrency(amount, currency);
 | 
						|
	}
 | 
						|
	if (message.has_reply_to_msg_id()) {
 | 
						|
		if (message.vaction.type() == mtpc_messageActionPinMessage) {
 | 
						|
			UpdateComponents(HistoryServicePinned::Bit());
 | 
						|
		}
 | 
						|
		if (auto dependent = GetDependentData()) {
 | 
						|
			dependent->msgId = message.vreply_to_msg_id.v;
 | 
						|
			if (!updateDependent() && App::api()) {
 | 
						|
				App::api()->requestMessageData(history()->peer->asChannel(), dependent->msgId, HistoryDependentItemCallback(fullId()));
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	setMessageByAction(message.vaction);
 | 
						|
}
 | 
						|
 | 
						|
void HistoryService::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) {
 | 
						|
	if (_media) _media->clickHandlerActiveChanged(p, active);
 | 
						|
	HistoryItem::clickHandlerActiveChanged(p, active);
 | 
						|
}
 | 
						|
 | 
						|
void HistoryService::clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) {
 | 
						|
	if (_media) _media->clickHandlerPressedChanged(p, pressed);
 | 
						|
	HistoryItem::clickHandlerPressedChanged(p, pressed);
 | 
						|
}
 | 
						|
 | 
						|
void HistoryService::applyEdition(const MTPDmessageService &message) {
 | 
						|
	clearDependency();
 | 
						|
	UpdateComponents(0);
 | 
						|
 | 
						|
	createFromMtp(message);
 | 
						|
 | 
						|
	if (message.vaction.type() == mtpc_messageActionHistoryClear) {
 | 
						|
		removeMedia();
 | 
						|
		finishEditionToEmpty();
 | 
						|
	} else {
 | 
						|
		finishEdition(-1);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryService::removeMedia() {
 | 
						|
	if (!_media) return;
 | 
						|
 | 
						|
	bool mediaWasDisplayed = _media->isDisplayed();
 | 
						|
	_media.reset();
 | 
						|
	if (mediaWasDisplayed) {
 | 
						|
		_textWidth = -1;
 | 
						|
		_textHeight = 0;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
int32 HistoryService::addToOverview(AddToOverviewMethod method) {
 | 
						|
	if (!indexInOverview()) return 0;
 | 
						|
 | 
						|
	int32 result = 0;
 | 
						|
	if (auto media = getMedia()) {
 | 
						|
		result |= media->addToOverview(method);
 | 
						|
	}
 | 
						|
	return result;
 | 
						|
}
 | 
						|
 | 
						|
void HistoryService::eraseFromOverview() {
 | 
						|
	if (auto media = getMedia()) {
 | 
						|
		media->eraseFromOverview();
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void HistoryService::updateDependentText() {
 | 
						|
	auto text = PreparedText {};
 | 
						|
	if (Has<HistoryServicePinned>()) {
 | 
						|
		text = preparePinnedText();
 | 
						|
	} else if (Has<HistoryServiceGameScore>()) {
 | 
						|
		text = prepareGameScoreText();
 | 
						|
	} else if (Has<HistoryServicePayment>()) {
 | 
						|
		text = preparePaymentSentText();
 | 
						|
	} else {
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	setServiceText(text);
 | 
						|
	if (history()->textCachedFor == this) {
 | 
						|
		history()->textCachedFor = nullptr;
 | 
						|
	}
 | 
						|
	if (App::main()) {
 | 
						|
		App::main()->dlgUpdated(history()->peer, id);
 | 
						|
	}
 | 
						|
	App::historyUpdateDependent(this);
 | 
						|
}
 | 
						|
 | 
						|
void HistoryService::clearDependency() {
 | 
						|
	if (auto dependent = GetDependentData()) {
 | 
						|
		if (dependent->msg) {
 | 
						|
			App::historyUnregDependency(this, dependent->msg);
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
HistoryService::~HistoryService() {
 | 
						|
	clearDependency();
 | 
						|
	_media.reset();
 | 
						|
}
 | 
						|
 | 
						|
HistoryJoined::HistoryJoined(gsl::not_null<History*> history, const QDateTime &inviteDate, gsl::not_null<UserData*> inviter, MTPDmessage::Flags flags)
 | 
						|
	: HistoryService(history, clientMsgId(), inviteDate, GenerateText(history, inviter), flags) {
 | 
						|
}
 | 
						|
 | 
						|
HistoryJoined::PreparedText HistoryJoined::GenerateText(gsl::not_null<History*> history, gsl::not_null<UserData*> inviter) {
 | 
						|
	if (inviter->id == AuthSession::CurrentUserPeerId()) {
 | 
						|
		return { lang(history->isMegagroup() ? lng_action_you_joined_group : lng_action_you_joined) };
 | 
						|
	}
 | 
						|
	auto result = PreparedText {};
 | 
						|
	result.links.push_back(peerOpenClickHandler(inviter));
 | 
						|
	if (history->isMegagroup()) {
 | 
						|
		result.text = lng_action_add_you_group(lt_from, textcmdLink(1, inviter->name));
 | 
						|
	}
 | 
						|
	result.text = lng_action_add_you(lt_from, textcmdLink(1, inviter->name));
 | 
						|
	return result;
 | 
						|
}
 |