mirror of https://github.com/procxx/kepka.git
				
				
				
			Copy text with expanded links only to external.
Paste valid custom links in message field if copied from messages.
This commit is contained in:
		
							parent
							
								
									0f0c3b7461
								
							
						
					
					
						commit
						b5be6df5e2
					
				| 
						 | 
					@ -486,8 +486,7 @@ DeleteMessagesBox::DeleteMessagesBox(
 | 
				
			||||||
void DeleteMessagesBox::prepare() {
 | 
					void DeleteMessagesBox::prepare() {
 | 
				
			||||||
	auto details = TextWithEntities();
 | 
						auto details = TextWithEntities();
 | 
				
			||||||
	const auto appendDetails = [&](TextWithEntities &&text) {
 | 
						const auto appendDetails = [&](TextWithEntities &&text) {
 | 
				
			||||||
		TextUtilities::Append(details, { "\n\n" });
 | 
							details.append(qstr("\n\n")).append(std::move(text));
 | 
				
			||||||
		TextUtilities::Append(details, std::move(text));
 | 
					 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
	auto deleteKey = lng_box_delete;
 | 
						auto deleteKey = lng_box_delete;
 | 
				
			||||||
	auto deleteStyle = &st::defaultBoxButton;
 | 
						auto deleteStyle = &st::defaultBoxButton;
 | 
				
			||||||
| 
						 | 
					@ -648,7 +647,7 @@ auto DeleteMessagesBox::revokeText(not_null<PeerData*> peer) const
 | 
				
			||||||
		if (const auto user = peer->asUser()) {
 | 
							if (const auto user = peer->asUser()) {
 | 
				
			||||||
			auto boldName = TextWithEntities{ user->firstName };
 | 
								auto boldName = TextWithEntities{ user->firstName };
 | 
				
			||||||
			boldName.entities.push_back(
 | 
								boldName.entities.push_back(
 | 
				
			||||||
				EntityInText(EntityInTextBold, 0, boldName.text.size()));
 | 
									{ EntityType::Bold, 0, boldName.text.size() });
 | 
				
			||||||
			if (canRevokeOutgoingCount == 1) {
 | 
								if (canRevokeOutgoingCount == 1) {
 | 
				
			||||||
				result.description = lng_selected_unsend_about_user_one__generic<TextWithEntities>(
 | 
									result.description = lng_selected_unsend_about_user_one__generic<TextWithEntities>(
 | 
				
			||||||
					lt_user,
 | 
										lt_user,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -229,7 +229,7 @@ void ConfirmPhoneBox::prepare() {
 | 
				
			||||||
	aboutText.text = lng_confirm_phone_about(lt_phone, formattedPhone);
 | 
						aboutText.text = lng_confirm_phone_about(lt_phone, formattedPhone);
 | 
				
			||||||
	auto phonePosition = aboutText.text.indexOf(formattedPhone);
 | 
						auto phonePosition = aboutText.text.indexOf(formattedPhone);
 | 
				
			||||||
	if (phonePosition >= 0) {
 | 
						if (phonePosition >= 0) {
 | 
				
			||||||
		aboutText.entities.push_back(EntityInText(EntityInTextBold, phonePosition, formattedPhone.size()));
 | 
							aboutText.entities.push_back({ EntityType::Bold, phonePosition, formattedPhone.size() });
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	_about->setMarkedText(aboutText);
 | 
						_about->setMarkedText(aboutText);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -627,11 +627,11 @@ void Controller::refreshEditInviteLink() {
 | 
				
			||||||
		if (text.text.startsWith(remove)) {
 | 
							if (text.text.startsWith(remove)) {
 | 
				
			||||||
			text.text.remove(0, remove.size());
 | 
								text.text.remove(0, remove.size());
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		text.entities.push_back(EntityInText(
 | 
							text.entities.push_back({
 | 
				
			||||||
			EntityInTextCustomUrl,
 | 
								EntityType::CustomUrl,
 | 
				
			||||||
			0,
 | 
								0,
 | 
				
			||||||
			text.text.size(),
 | 
								text.text.size(),
 | 
				
			||||||
			link));
 | 
								link });
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	_controls.inviteLink->setMarkedText(text);
 | 
						_controls.inviteLink->setMarkedText(text);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -197,25 +197,25 @@ EntitiesInText ConvertTextTagsToEntities(const TextWithTags::Tags &tags) {
 | 
				
			||||||
	result.reserve(tags.size());
 | 
						result.reserve(tags.size());
 | 
				
			||||||
	for (const auto &tag : tags) {
 | 
						for (const auto &tag : tags) {
 | 
				
			||||||
		const auto push = [&](
 | 
							const auto push = [&](
 | 
				
			||||||
				EntityInTextType type,
 | 
									EntityType type,
 | 
				
			||||||
				const QString &data = QString()) {
 | 
									const QString &data = QString()) {
 | 
				
			||||||
			result.push_back(
 | 
								result.push_back(
 | 
				
			||||||
				EntityInText(type, tag.offset, tag.length, data));
 | 
									EntityInText(type, tag.offset, tag.length, data));
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
		if (IsMentionLink(tag.id)) {
 | 
							if (IsMentionLink(tag.id)) {
 | 
				
			||||||
			if (auto match = qthelp::regex_match("^(\\d+\\.\\d+)(/|$)", tag.id.midRef(kMentionTagStart.size()))) {
 | 
								if (auto match = qthelp::regex_match("^(\\d+\\.\\d+)(/|$)", tag.id.midRef(kMentionTagStart.size()))) {
 | 
				
			||||||
				push(EntityInTextMentionName, match->captured(1));
 | 
									push(EntityType::MentionName, match->captured(1));
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		} else if (tag.id == Ui::InputField::kTagBold) {
 | 
							} else if (tag.id == Ui::InputField::kTagBold) {
 | 
				
			||||||
			push(EntityInTextBold);
 | 
								push(EntityType::Bold);
 | 
				
			||||||
		} else if (tag.id == Ui::InputField::kTagItalic) {
 | 
							} else if (tag.id == Ui::InputField::kTagItalic) {
 | 
				
			||||||
			push(EntityInTextItalic);
 | 
								push(EntityType::Italic);
 | 
				
			||||||
		} else if (tag.id == Ui::InputField::kTagCode) {
 | 
							} else if (tag.id == Ui::InputField::kTagCode) {
 | 
				
			||||||
			push(EntityInTextCode);
 | 
								push(EntityType::Code);
 | 
				
			||||||
		} else if (tag.id == Ui::InputField::kTagPre) {
 | 
							} else if (tag.id == Ui::InputField::kTagPre) {
 | 
				
			||||||
			push(EntityInTextPre);
 | 
								push(EntityType::Pre);
 | 
				
			||||||
		} else /*if (ValidateUrl(tag.id)) */{ // We validate when we insert.
 | 
							} else /*if (ValidateUrl(tag.id)) */{ // We validate when we insert.
 | 
				
			||||||
			push(EntityInTextCustomUrl, tag.id);
 | 
								push(EntityType::CustomUrl, tag.id);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return result;
 | 
						return result;
 | 
				
			||||||
| 
						 | 
					@ -233,41 +233,44 @@ TextWithTags::Tags ConvertEntitiesToTextTags(const EntitiesInText &entities) {
 | 
				
			||||||
			result.push_back({ entity.offset(), entity.length(), tag });
 | 
								result.push_back({ entity.offset(), entity.length(), tag });
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
		switch (entity.type()) {
 | 
							switch (entity.type()) {
 | 
				
			||||||
		case EntityInTextMentionName: {
 | 
							case EntityType::MentionName: {
 | 
				
			||||||
			auto match = QRegularExpression("^(\\d+\\.\\d+)$").match(entity.data());
 | 
								auto match = QRegularExpression(R"(^(\d+\.\d+)$)").match(entity.data());
 | 
				
			||||||
			if (match.hasMatch()) {
 | 
								if (match.hasMatch()) {
 | 
				
			||||||
				push(kMentionTagStart + entity.data());
 | 
									push(kMentionTagStart + entity.data());
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		} break;
 | 
							} break;
 | 
				
			||||||
		case EntityInTextCustomUrl: {
 | 
							case EntityType::CustomUrl: {
 | 
				
			||||||
			const auto url = entity.data();
 | 
								const auto url = entity.data();
 | 
				
			||||||
			if (Ui::InputField::IsValidMarkdownLink(url)
 | 
								if (Ui::InputField::IsValidMarkdownLink(url)
 | 
				
			||||||
				&& !IsMentionLink(url)) {
 | 
									&& !IsMentionLink(url)) {
 | 
				
			||||||
				push(url);
 | 
									push(url);
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		} break;
 | 
							} break;
 | 
				
			||||||
		case EntityInTextBold: push(Ui::InputField::kTagBold); break;
 | 
							case EntityType::Bold: push(Ui::InputField::kTagBold); break;
 | 
				
			||||||
		case EntityInTextItalic: push(Ui::InputField::kTagItalic); break;
 | 
							case EntityType::Italic: push(Ui::InputField::kTagItalic); break;
 | 
				
			||||||
		case EntityInTextCode: push(Ui::InputField::kTagCode); break;
 | 
							case EntityType::Code: push(Ui::InputField::kTagCode); break;
 | 
				
			||||||
		case EntityInTextPre: push(Ui::InputField::kTagPre); break;
 | 
							case EntityType::Pre: push(Ui::InputField::kTagPre); break;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return result;
 | 
						return result;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
std::unique_ptr<QMimeData> MimeDataFromTextWithEntities(
 | 
					std::unique_ptr<QMimeData> MimeDataFromText(
 | 
				
			||||||
		const TextWithEntities &forClipboard) {
 | 
							const TextForMimeData &text) {
 | 
				
			||||||
	if (forClipboard.text.isEmpty()) {
 | 
						if (text.empty()) {
 | 
				
			||||||
		return nullptr;
 | 
							return nullptr;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	auto result = std::make_unique<QMimeData>();
 | 
						auto result = std::make_unique<QMimeData>();
 | 
				
			||||||
	result->setText(forClipboard.text);
 | 
						result->setText(text.expanded);
 | 
				
			||||||
	auto tags = ConvertEntitiesToTextTags(forClipboard.entities);
 | 
						auto tags = ConvertEntitiesToTextTags(text.rich.entities);
 | 
				
			||||||
	if (!tags.isEmpty()) {
 | 
						if (!tags.isEmpty()) {
 | 
				
			||||||
		for (auto &tag : tags) {
 | 
							for (auto &tag : tags) {
 | 
				
			||||||
			tag.id = ConvertTagToMimeTag(tag.id);
 | 
								tag.id = ConvertTagToMimeTag(tag.id);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
							result->setData(
 | 
				
			||||||
 | 
								TextUtilities::TagsTextMimeType(),
 | 
				
			||||||
 | 
								text.rich.text.toUtf8());
 | 
				
			||||||
		result->setData(
 | 
							result->setData(
 | 
				
			||||||
			TextUtilities::TagsMimeType(),
 | 
								TextUtilities::TagsMimeType(),
 | 
				
			||||||
			TextUtilities::SerializeTags(tags));
 | 
								TextUtilities::SerializeTags(tags));
 | 
				
			||||||
| 
						 | 
					@ -275,10 +278,10 @@ std::unique_ptr<QMimeData> MimeDataFromTextWithEntities(
 | 
				
			||||||
	return result;
 | 
						return result;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void SetClipboardWithEntities(
 | 
					void SetClipboardText(
 | 
				
			||||||
		const TextWithEntities &forClipboard,
 | 
							const TextForMimeData &text,
 | 
				
			||||||
		QClipboard::Mode mode) {
 | 
							QClipboard::Mode mode) {
 | 
				
			||||||
	if (auto data = MimeDataFromTextWithEntities(forClipboard)) {
 | 
						if (auto data = MimeDataFromText(text)) {
 | 
				
			||||||
		QApplication::clipboard()->setMimeData(data.release(), mode);
 | 
							QApplication::clipboard()->setMimeData(data.release(), mode);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -21,10 +21,9 @@ QString PrepareMentionTag(not_null<UserData*> user);
 | 
				
			||||||
EntitiesInText ConvertTextTagsToEntities(const TextWithTags::Tags &tags);
 | 
					EntitiesInText ConvertTextTagsToEntities(const TextWithTags::Tags &tags);
 | 
				
			||||||
TextWithTags::Tags ConvertEntitiesToTextTags(
 | 
					TextWithTags::Tags ConvertEntitiesToTextTags(
 | 
				
			||||||
	const EntitiesInText &entities);
 | 
						const EntitiesInText &entities);
 | 
				
			||||||
std::unique_ptr<QMimeData> MimeDataFromTextWithEntities(
 | 
					std::unique_ptr<QMimeData> MimeDataFromText(const TextForMimeData &text);
 | 
				
			||||||
	const TextWithEntities &forClipboard);
 | 
					void SetClipboardText(
 | 
				
			||||||
void SetClipboardWithEntities(
 | 
						const TextForMimeData &text,
 | 
				
			||||||
	const TextWithEntities &forClipboard,
 | 
					 | 
				
			||||||
	QClipboard::Mode mode = QClipboard::Clipboard);
 | 
						QClipboard::Mode mode = QClipboard::Clipboard);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Fn<bool(
 | 
					Fn<bool(
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -50,12 +50,6 @@ bool ClickHandler::setActive(const ClickHandlerPtr &p, ClickHandlerHost *host) {
 | 
				
			||||||
	return true;
 | 
						return true;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
TextWithEntities ClickHandler::getExpandedLinkTextWithEntities(int entityOffset, const QStringRef &textPart) const {
 | 
					auto ClickHandler::getTextEntity() const -> TextEntity {
 | 
				
			||||||
	return { QString(), EntitiesInText() };
 | 
						return { EntityType::Invalid };
 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
TextWithEntities ClickHandler::simpleTextWithEntity(const EntityInText &entity) const {
 | 
					 | 
				
			||||||
	TextWithEntities result;
 | 
					 | 
				
			||||||
	result.entities.push_back(entity);
 | 
					 | 
				
			||||||
	return result;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -15,11 +15,6 @@ struct ClickContext {
 | 
				
			||||||
	QVariant other;
 | 
						QVariant other;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
enum ExpandLinksMode {
 | 
					 | 
				
			||||||
	ExpandLinksShortened,
 | 
					 | 
				
			||||||
	ExpandLinksAll,
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class ClickHandlerHost {
 | 
					class ClickHandlerHost {
 | 
				
			||||||
protected:
 | 
					protected:
 | 
				
			||||||
	virtual void clickHandlerActiveChanged(const ClickHandlerPtr &action, bool active) {
 | 
						virtual void clickHandlerActiveChanged(const ClickHandlerPtr &action, bool active) {
 | 
				
			||||||
| 
						 | 
					@ -31,8 +26,7 @@ protected:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class EntityInText;
 | 
					enum class EntityType;
 | 
				
			||||||
struct TextWithEntities;
 | 
					 | 
				
			||||||
class ClickHandler {
 | 
					class ClickHandler {
 | 
				
			||||||
public:
 | 
					public:
 | 
				
			||||||
	virtual ~ClickHandler() {
 | 
						virtual ~ClickHandler() {
 | 
				
			||||||
| 
						 | 
					@ -59,9 +53,11 @@ public:
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Entities in text support.
 | 
						// Entities in text support.
 | 
				
			||||||
 | 
						struct TextEntity {
 | 
				
			||||||
	// This method returns empty string if just textPart should be used (nothing to expand).
 | 
							EntityType type = EntityType();
 | 
				
			||||||
	virtual TextWithEntities getExpandedLinkTextWithEntities(int entityOffset, const QStringRef &textPart) const;
 | 
							QString data;
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
						virtual TextEntity getTextEntity() const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// This method should be called on mouse over a click handler.
 | 
						// This method should be called on mouse over a click handler.
 | 
				
			||||||
	// It returns true if the active handler was changed or false otherwise.
 | 
						// It returns true if the active handler was changed or false otherwise.
 | 
				
			||||||
| 
						 | 
					@ -146,11 +142,6 @@ public:
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
protected:
 | 
					 | 
				
			||||||
	// For click handlers like mention or hashtag in getExpandedLinkTextWithEntities()
 | 
					 | 
				
			||||||
	// we return just an empty string ("use original string part") with single entity.
 | 
					 | 
				
			||||||
	TextWithEntities simpleTextWithEntity(const EntityInText &entity) const;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
private:
 | 
					private:
 | 
				
			||||||
	static NeverFreedPointer<ClickHandlerPtr> _active;
 | 
						static NeverFreedPointer<ClickHandlerPtr> _active;
 | 
				
			||||||
	static NeverFreedPointer<ClickHandlerPtr> _pressed;
 | 
						static NeverFreedPointer<ClickHandlerPtr> _pressed;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -133,15 +133,11 @@ void UrlClickHandler::Open(QString url, QVariant context) {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
TextWithEntities UrlClickHandler::getExpandedLinkTextWithEntities(int entityOffset, const QStringRef &textPart) const {
 | 
					auto UrlClickHandler::getTextEntity() const -> TextEntity {
 | 
				
			||||||
	auto result = TextWithEntities();
 | 
						const auto type = isEmail(_originalUrl)
 | 
				
			||||||
	result.text = _originalUrl;
 | 
							? EntityType::Email
 | 
				
			||||||
	const auto entityLength = _originalUrl.size();
 | 
							: EntityType::Url;
 | 
				
			||||||
	const auto entityType = isEmail(_originalUrl)
 | 
						return { type, _originalUrl };
 | 
				
			||||||
		? EntityInTextEmail
 | 
					 | 
				
			||||||
		: EntityInTextUrl;
 | 
					 | 
				
			||||||
	result.entities.push_back({ entityType, entityOffset, entityLength });
 | 
					 | 
				
			||||||
	return result;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void HiddenUrlClickHandler::Open(QString url, QVariant context) {
 | 
					void HiddenUrlClickHandler::Open(QString url, QVariant context) {
 | 
				
			||||||
| 
						 | 
					@ -200,8 +196,8 @@ void BotGameUrlClickHandler::onClick(ClickContext context) const {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
TextWithEntities HiddenUrlClickHandler::getExpandedLinkTextWithEntities(int entityOffset, const QStringRef &textPart) const {
 | 
					auto HiddenUrlClickHandler::getTextEntity() const -> TextEntity {
 | 
				
			||||||
	return simpleTextWithEntity({ EntityInTextCustomUrl, entityOffset, textPart.size(), url() });
 | 
						return { EntityType::CustomUrl, url() };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
QString MentionClickHandler::copyToClipboardContextItemText() const {
 | 
					QString MentionClickHandler::copyToClipboardContextItemText() const {
 | 
				
			||||||
| 
						 | 
					@ -215,8 +211,8 @@ void MentionClickHandler::onClick(ClickContext context) const {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
TextWithEntities MentionClickHandler::getExpandedLinkTextWithEntities(int entityOffset, const QStringRef &textPart) const {
 | 
					auto MentionClickHandler::getTextEntity() const -> TextEntity {
 | 
				
			||||||
	return simpleTextWithEntity({ EntityInTextMention, entityOffset, textPart.size() });
 | 
						return { EntityType::Mention };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void MentionNameClickHandler::onClick(ClickContext context) const {
 | 
					void MentionNameClickHandler::onClick(ClickContext context) const {
 | 
				
			||||||
| 
						 | 
					@ -228,9 +224,9 @@ void MentionNameClickHandler::onClick(ClickContext context) const {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
TextWithEntities MentionNameClickHandler::getExpandedLinkTextWithEntities(int entityOffset, const QStringRef &textPart) const {
 | 
					auto MentionNameClickHandler::getTextEntity() const -> TextEntity {
 | 
				
			||||||
	auto data = QString::number(_userId) + '.' + QString::number(_accessHash);
 | 
						auto data = QString::number(_userId) + '.' + QString::number(_accessHash);
 | 
				
			||||||
	return simpleTextWithEntity({ EntityInTextMentionName, entityOffset, textPart.size(), data });
 | 
						return { EntityType::MentionName, data };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
QString MentionNameClickHandler::tooltip() const {
 | 
					QString MentionNameClickHandler::tooltip() const {
 | 
				
			||||||
| 
						 | 
					@ -254,8 +250,8 @@ void HashtagClickHandler::onClick(ClickContext context) const {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
TextWithEntities HashtagClickHandler::getExpandedLinkTextWithEntities(int entityOffset, const QStringRef &textPart) const {
 | 
					auto HashtagClickHandler::getTextEntity() const -> TextEntity {
 | 
				
			||||||
	return simpleTextWithEntity({ EntityInTextHashtag, entityOffset, textPart.size() });
 | 
						return { EntityType::Hashtag };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
QString CashtagClickHandler::copyToClipboardContextItemText() const {
 | 
					QString CashtagClickHandler::copyToClipboardContextItemText() const {
 | 
				
			||||||
| 
						 | 
					@ -269,10 +265,8 @@ void CashtagClickHandler::onClick(ClickContext context) const {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
TextWithEntities CashtagClickHandler::getExpandedLinkTextWithEntities(
 | 
					auto CashtagClickHandler::getTextEntity() const -> TextEntity {
 | 
				
			||||||
		int entityOffset,
 | 
						return { EntityType::Cashtag };
 | 
				
			||||||
		const QStringRef &textPart) const {
 | 
					 | 
				
			||||||
	return simpleTextWithEntity({ EntityInTextCashtag, entityOffset, textPart.size() });
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
PeerData *BotCommandClickHandler::_peer = nullptr;
 | 
					PeerData *BotCommandClickHandler::_peer = nullptr;
 | 
				
			||||||
| 
						 | 
					@ -304,6 +298,6 @@ void BotCommandClickHandler::onClick(ClickContext context) const {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
TextWithEntities BotCommandClickHandler::getExpandedLinkTextWithEntities(int entityOffset, const QStringRef &textPart) const {
 | 
					auto BotCommandClickHandler::getTextEntity() const -> TextEntity {
 | 
				
			||||||
	return simpleTextWithEntity({ EntityInTextBotCommand, entityOffset, textPart.size() });
 | 
						return { EntityType::BotCommand };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -47,9 +47,7 @@ public:
 | 
				
			||||||
		return url();
 | 
							return url();
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	TextWithEntities getExpandedLinkTextWithEntities(
 | 
						TextEntity getTextEntity() const override;
 | 
				
			||||||
		int entityOffset,
 | 
					 | 
				
			||||||
		const QStringRef &textPart) const override;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	static void Open(QString url, QVariant context = {});
 | 
						static void Open(QString url, QVariant context = {});
 | 
				
			||||||
	void onClick(ClickContext context) const override {
 | 
						void onClick(ClickContext context) const override {
 | 
				
			||||||
| 
						 | 
					@ -96,9 +94,7 @@ public:
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	TextWithEntities getExpandedLinkTextWithEntities(
 | 
						TextEntity getTextEntity() const override;
 | 
				
			||||||
		int entityOffset,
 | 
					 | 
				
			||||||
		const QStringRef &textPart) const override;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -128,9 +124,7 @@ public:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	QString copyToClipboardContextItemText() const override;
 | 
						QString copyToClipboardContextItemText() const override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	TextWithEntities getExpandedLinkTextWithEntities(
 | 
						TextEntity getTextEntity() const override;
 | 
				
			||||||
		int entityOffset,
 | 
					 | 
				
			||||||
		const QStringRef &textPart) const override;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
protected:
 | 
					protected:
 | 
				
			||||||
	QString url() const override {
 | 
						QString url() const override {
 | 
				
			||||||
| 
						 | 
					@ -152,9 +146,7 @@ public:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	void onClick(ClickContext context) const override;
 | 
						void onClick(ClickContext context) const override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	TextWithEntities getExpandedLinkTextWithEntities(
 | 
						TextEntity getTextEntity() const override;
 | 
				
			||||||
		int entityOffset,
 | 
					 | 
				
			||||||
		const QStringRef &textPart) const override;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	QString tooltip() const override;
 | 
						QString tooltip() const override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -178,9 +170,7 @@ public:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	QString copyToClipboardContextItemText() const override;
 | 
						QString copyToClipboardContextItemText() const override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	TextWithEntities getExpandedLinkTextWithEntities(
 | 
						TextEntity getTextEntity() const override;
 | 
				
			||||||
		int entityOffset,
 | 
					 | 
				
			||||||
		const QStringRef &textPart) const override;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
protected:
 | 
					protected:
 | 
				
			||||||
	QString url() const override {
 | 
						QString url() const override {
 | 
				
			||||||
| 
						 | 
					@ -205,9 +195,7 @@ public:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	QString copyToClipboardContextItemText() const override;
 | 
						QString copyToClipboardContextItemText() const override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	TextWithEntities getExpandedLinkTextWithEntities(
 | 
						TextEntity getTextEntity() const override;
 | 
				
			||||||
		int entityOffset,
 | 
					 | 
				
			||||||
		const QStringRef &textPart) const override;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
protected:
 | 
					protected:
 | 
				
			||||||
	QString url() const override {
 | 
						QString url() const override {
 | 
				
			||||||
| 
						 | 
					@ -239,9 +227,7 @@ public:
 | 
				
			||||||
		_bot = bot;
 | 
							_bot = bot;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	TextWithEntities getExpandedLinkTextWithEntities(
 | 
						TextEntity getTextEntity() const override;
 | 
				
			||||||
		int entityOffset,
 | 
					 | 
				
			||||||
		const QStringRef &textPart) const override;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
protected:
 | 
					protected:
 | 
				
			||||||
	QString url() const override {
 | 
						QString url() const override {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -120,15 +120,14 @@ QString WithCaptionNotificationText(
 | 
				
			||||||
 | 
					
 | 
				
			||||||
} // namespace
 | 
					} // namespace
 | 
				
			||||||
 | 
					
 | 
				
			||||||
TextWithEntities WithCaptionClipboardText(
 | 
					TextForMimeData WithCaptionClipboardText(
 | 
				
			||||||
		const QString &attachType,
 | 
							const QString &attachType,
 | 
				
			||||||
		TextWithEntities &&caption) {
 | 
							TextForMimeData &&caption) {
 | 
				
			||||||
	TextWithEntities result;
 | 
						auto result = TextForMimeData();
 | 
				
			||||||
	result.text.reserve(5 + attachType.size() + caption.text.size());
 | 
						result.reserve(5 + attachType.size() + caption.expanded.size());
 | 
				
			||||||
	result.text.append(qstr("[ ")).append(attachType).append(qstr(" ]"));
 | 
						result.append(qstr("[ ")).append(attachType).append(qstr(" ]"));
 | 
				
			||||||
	if (!caption.text.isEmpty()) {
 | 
						if (!caption.empty()) {
 | 
				
			||||||
		result.text.append(qstr("\n"));
 | 
							result.append('\n').append(std::move(caption));
 | 
				
			||||||
		TextUtilities::Append(result, std::move(caption));
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return result;
 | 
						return result;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -321,7 +320,7 @@ QString MediaPhoto::pinnedTextSubstring() const {
 | 
				
			||||||
	return lang(lng_action_pinned_media_photo);
 | 
						return lang(lng_action_pinned_media_photo);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
TextWithEntities MediaPhoto::clipboardText() const {
 | 
					TextForMimeData MediaPhoto::clipboardText() const {
 | 
				
			||||||
	return WithCaptionClipboardText(
 | 
						return WithCaptionClipboardText(
 | 
				
			||||||
		lang(lng_in_dlg_photo),
 | 
							lang(lng_in_dlg_photo),
 | 
				
			||||||
		parent()->clipboardText());
 | 
							parent()->clipboardText());
 | 
				
			||||||
| 
						 | 
					@ -629,7 +628,7 @@ QString MediaFile::pinnedTextSubstring() const {
 | 
				
			||||||
	return lang(lng_action_pinned_media_file);
 | 
						return lang(lng_action_pinned_media_file);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
TextWithEntities MediaFile::clipboardText() const {
 | 
					TextForMimeData MediaFile::clipboardText() const {
 | 
				
			||||||
	const auto attachType = [&] {
 | 
						const auto attachType = [&] {
 | 
				
			||||||
		const auto name = _document->composeNameString();
 | 
							const auto name = _document->composeNameString();
 | 
				
			||||||
		const auto addName = !name.isEmpty()
 | 
							const auto addName = !name.isEmpty()
 | 
				
			||||||
| 
						 | 
					@ -816,7 +815,7 @@ QString MediaContact::pinnedTextSubstring() const {
 | 
				
			||||||
	return lang(lng_action_pinned_media_contact);
 | 
						return lang(lng_action_pinned_media_contact);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
TextWithEntities MediaContact::clipboardText() const {
 | 
					TextForMimeData MediaContact::clipboardText() const {
 | 
				
			||||||
	const auto text = qsl("[ ") + lang(lng_in_dlg_contact) + qsl(" ]\n")
 | 
						const auto text = qsl("[ ") + lang(lng_in_dlg_contact) + qsl(" ]\n")
 | 
				
			||||||
		+ lng_full_name(
 | 
							+ lng_full_name(
 | 
				
			||||||
			lt_first_name,
 | 
								lt_first_name,
 | 
				
			||||||
| 
						 | 
					@ -825,7 +824,7 @@ TextWithEntities MediaContact::clipboardText() const {
 | 
				
			||||||
			_contact.lastName).trimmed()
 | 
								_contact.lastName).trimmed()
 | 
				
			||||||
		+ '\n'
 | 
							+ '\n'
 | 
				
			||||||
		+ _contact.phoneNumber;
 | 
							+ _contact.phoneNumber;
 | 
				
			||||||
	return { text, EntitiesInText() };
 | 
						return TextForMimeData::Simple(text);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bool MediaContact::updateInlineResultMedia(const MTPMessageMedia &media) {
 | 
					bool MediaContact::updateInlineResultMedia(const MTPMessageMedia &media) {
 | 
				
			||||||
| 
						 | 
					@ -900,26 +899,22 @@ QString MediaLocation::pinnedTextSubstring() const {
 | 
				
			||||||
	return lang(lng_action_pinned_media_location);
 | 
						return lang(lng_action_pinned_media_location);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
TextWithEntities MediaLocation::clipboardText() const {
 | 
					TextForMimeData MediaLocation::clipboardText() const {
 | 
				
			||||||
	TextWithEntities result = {
 | 
						auto result = TextForMimeData::Simple(
 | 
				
			||||||
		qsl("[ ") + lang(lng_maps_point) + qsl(" ]\n"),
 | 
							qstr("[ ") + lang(lng_maps_point) + qstr(" ]\n"));
 | 
				
			||||||
		EntitiesInText()
 | 
					 | 
				
			||||||
	};
 | 
					 | 
				
			||||||
	auto titleResult = TextUtilities::ParseEntities(
 | 
						auto titleResult = TextUtilities::ParseEntities(
 | 
				
			||||||
		TextUtilities::Clean(_title),
 | 
							TextUtilities::Clean(_title),
 | 
				
			||||||
		Ui::WebpageTextTitleOptions().flags);
 | 
							Ui::WebpageTextTitleOptions().flags);
 | 
				
			||||||
	auto descriptionResult = TextUtilities::ParseEntities(
 | 
						auto descriptionResult = TextUtilities::ParseEntities(
 | 
				
			||||||
		TextUtilities::Clean(_description),
 | 
							TextUtilities::Clean(_description),
 | 
				
			||||||
		TextParseLinks | TextParseMultiline | TextParseRichText);
 | 
							TextParseLinks | TextParseMultiline | TextParseRichText);
 | 
				
			||||||
	if (!titleResult.text.isEmpty()) {
 | 
						if (!titleResult.empty()) {
 | 
				
			||||||
		TextUtilities::Append(result, std::move(titleResult));
 | 
							result.append(std::move(titleResult));
 | 
				
			||||||
		result.text.append('\n');
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if (!descriptionResult.text.isEmpty()) {
 | 
						if (!descriptionResult.text.isEmpty()) {
 | 
				
			||||||
		TextUtilities::Append(result, std::move(descriptionResult));
 | 
							result.append(std::move(descriptionResult));
 | 
				
			||||||
		result.text.append('\n');
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	result.text += LocationClickHandler(_location->coords).dragText();
 | 
						result.append(LocationClickHandler(_location->coords).dragText());
 | 
				
			||||||
	return result;
 | 
						return result;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -972,8 +967,9 @@ QString MediaCall::pinnedTextSubstring() const {
 | 
				
			||||||
	return QString();
 | 
						return QString();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
TextWithEntities MediaCall::clipboardText() const {
 | 
					TextForMimeData MediaCall::clipboardText() const {
 | 
				
			||||||
	return { qsl("[ ") + notificationText() + qsl(" ]"), EntitiesInText() };
 | 
						return TextForMimeData::Simple(
 | 
				
			||||||
 | 
							qstr("[ ") + notificationText() + qstr(" ]"));
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bool MediaCall::allowsForward() const {
 | 
					bool MediaCall::allowsForward() const {
 | 
				
			||||||
| 
						 | 
					@ -1071,8 +1067,8 @@ QString MediaWebPage::pinnedTextSubstring() const {
 | 
				
			||||||
	return QString();
 | 
						return QString();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
TextWithEntities MediaWebPage::clipboardText() const {
 | 
					TextForMimeData MediaWebPage::clipboardText() const {
 | 
				
			||||||
	return TextWithEntities();
 | 
						return TextForMimeData();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bool MediaWebPage::allowsEdit() const {
 | 
					bool MediaWebPage::allowsEdit() const {
 | 
				
			||||||
| 
						 | 
					@ -1145,8 +1141,8 @@ QString MediaGame::pinnedTextSubstring() const {
 | 
				
			||||||
	return lng_action_pinned_media_game(lt_game, title);
 | 
						return lng_action_pinned_media_game(lt_game, title);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
TextWithEntities MediaGame::clipboardText() const {
 | 
					TextForMimeData MediaGame::clipboardText() const {
 | 
				
			||||||
	return TextWithEntities();
 | 
						return TextForMimeData();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
QString MediaGame::errorTextForForward(not_null<PeerData*> peer) const {
 | 
					QString MediaGame::errorTextForForward(not_null<PeerData*> peer) const {
 | 
				
			||||||
| 
						 | 
					@ -1228,8 +1224,8 @@ QString MediaInvoice::pinnedTextSubstring() const {
 | 
				
			||||||
	return QString();
 | 
						return QString();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
TextWithEntities MediaInvoice::clipboardText() const {
 | 
					TextForMimeData MediaInvoice::clipboardText() const {
 | 
				
			||||||
	return TextWithEntities();
 | 
						return TextForMimeData();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bool MediaInvoice::updateInlineResultMedia(const MTPMessageMedia &media) {
 | 
					bool MediaInvoice::updateInlineResultMedia(const MTPMessageMedia &media) {
 | 
				
			||||||
| 
						 | 
					@ -1272,12 +1268,12 @@ QString MediaPoll::pinnedTextSubstring() const {
 | 
				
			||||||
	return QChar(171) + _poll->question + QChar(187);
 | 
						return QChar(171) + _poll->question + QChar(187);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
TextWithEntities MediaPoll::clipboardText() const {
 | 
					TextForMimeData MediaPoll::clipboardText() const {
 | 
				
			||||||
	const auto text = qsl("[ ")
 | 
						const auto text = qstr("[ ")
 | 
				
			||||||
		+ lang(lng_in_dlg_poll)
 | 
							+ lang(lng_in_dlg_poll)
 | 
				
			||||||
		+ qsl(" : ")
 | 
							+ qstr(" : ")
 | 
				
			||||||
		+ _poll->question
 | 
							+ _poll->question
 | 
				
			||||||
		+ qsl(" ]")
 | 
							+ qstr(" ]")
 | 
				
			||||||
		+ ranges::accumulate(
 | 
							+ ranges::accumulate(
 | 
				
			||||||
			ranges::view::all(
 | 
								ranges::view::all(
 | 
				
			||||||
				_poll->answers
 | 
									_poll->answers
 | 
				
			||||||
| 
						 | 
					@ -1285,7 +1281,7 @@ TextWithEntities MediaPoll::clipboardText() const {
 | 
				
			||||||
				return "\n- " + answer.text;
 | 
									return "\n- " + answer.text;
 | 
				
			||||||
			}),
 | 
								}),
 | 
				
			||||||
			QString());
 | 
								QString());
 | 
				
			||||||
	return { text, EntitiesInText() };
 | 
						return TextForMimeData::Simple(text);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
QString MediaPoll::errorTextForForward(not_null<PeerData*> peer) const {
 | 
					QString MediaPoll::errorTextForForward(not_null<PeerData*> peer) const {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -89,7 +89,7 @@ public:
 | 
				
			||||||
	virtual QString chatListText() const;
 | 
						virtual QString chatListText() const;
 | 
				
			||||||
	virtual QString notificationText() const = 0;
 | 
						virtual QString notificationText() const = 0;
 | 
				
			||||||
	virtual QString pinnedTextSubstring() const = 0;
 | 
						virtual QString pinnedTextSubstring() const = 0;
 | 
				
			||||||
	virtual TextWithEntities clipboardText() const = 0;
 | 
						virtual TextForMimeData clipboardText() const = 0;
 | 
				
			||||||
	virtual bool allowsForward() const;
 | 
						virtual bool allowsForward() const;
 | 
				
			||||||
	virtual bool allowsEdit() const;
 | 
						virtual bool allowsEdit() const;
 | 
				
			||||||
	virtual bool allowsEditCaption() const;
 | 
						virtual bool allowsEditCaption() const;
 | 
				
			||||||
| 
						 | 
					@ -140,7 +140,7 @@ public:
 | 
				
			||||||
	QString chatListText() const override;
 | 
						QString chatListText() const override;
 | 
				
			||||||
	QString notificationText() const override;
 | 
						QString notificationText() const override;
 | 
				
			||||||
	QString pinnedTextSubstring() const override;
 | 
						QString pinnedTextSubstring() const override;
 | 
				
			||||||
	TextWithEntities clipboardText() const override;
 | 
						TextForMimeData clipboardText() const override;
 | 
				
			||||||
	bool allowsEditCaption() const override;
 | 
						bool allowsEditCaption() const override;
 | 
				
			||||||
	bool allowsEditMedia() const override;
 | 
						bool allowsEditMedia() const override;
 | 
				
			||||||
	QString errorTextForForward(not_null<PeerData*> peer) const override;
 | 
						QString errorTextForForward(not_null<PeerData*> peer) const override;
 | 
				
			||||||
| 
						 | 
					@ -176,7 +176,7 @@ public:
 | 
				
			||||||
	QString chatListText() const override;
 | 
						QString chatListText() const override;
 | 
				
			||||||
	QString notificationText() const override;
 | 
						QString notificationText() const override;
 | 
				
			||||||
	QString pinnedTextSubstring() const override;
 | 
						QString pinnedTextSubstring() const override;
 | 
				
			||||||
	TextWithEntities clipboardText() const override;
 | 
						TextForMimeData clipboardText() const override;
 | 
				
			||||||
	bool allowsEditCaption() const override;
 | 
						bool allowsEditCaption() const override;
 | 
				
			||||||
	bool allowsEditMedia() const override;
 | 
						bool allowsEditMedia() const override;
 | 
				
			||||||
	bool forwardedBecomesUnread() const override;
 | 
						bool forwardedBecomesUnread() const override;
 | 
				
			||||||
| 
						 | 
					@ -209,7 +209,7 @@ public:
 | 
				
			||||||
	const SharedContact *sharedContact() const override;
 | 
						const SharedContact *sharedContact() const override;
 | 
				
			||||||
	QString notificationText() const override;
 | 
						QString notificationText() const override;
 | 
				
			||||||
	QString pinnedTextSubstring() const override;
 | 
						QString pinnedTextSubstring() const override;
 | 
				
			||||||
	TextWithEntities clipboardText() const override;
 | 
						TextForMimeData clipboardText() const override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	bool updateInlineResultMedia(const MTPMessageMedia &media) override;
 | 
						bool updateInlineResultMedia(const MTPMessageMedia &media) override;
 | 
				
			||||||
	bool updateSentMedia(const MTPMessageMedia &media) override;
 | 
						bool updateSentMedia(const MTPMessageMedia &media) override;
 | 
				
			||||||
| 
						 | 
					@ -239,7 +239,7 @@ public:
 | 
				
			||||||
	QString chatListText() const override;
 | 
						QString chatListText() const override;
 | 
				
			||||||
	QString notificationText() const override;
 | 
						QString notificationText() const override;
 | 
				
			||||||
	QString pinnedTextSubstring() const override;
 | 
						QString pinnedTextSubstring() const override;
 | 
				
			||||||
	TextWithEntities clipboardText() const override;
 | 
						TextForMimeData clipboardText() const override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	bool updateInlineResultMedia(const MTPMessageMedia &media) override;
 | 
						bool updateInlineResultMedia(const MTPMessageMedia &media) override;
 | 
				
			||||||
	bool updateSentMedia(const MTPMessageMedia &media) override;
 | 
						bool updateSentMedia(const MTPMessageMedia &media) override;
 | 
				
			||||||
| 
						 | 
					@ -265,7 +265,7 @@ public:
 | 
				
			||||||
	const Call *call() const override;
 | 
						const Call *call() const override;
 | 
				
			||||||
	QString notificationText() const override;
 | 
						QString notificationText() const override;
 | 
				
			||||||
	QString pinnedTextSubstring() const override;
 | 
						QString pinnedTextSubstring() const override;
 | 
				
			||||||
	TextWithEntities clipboardText() const override;
 | 
						TextForMimeData clipboardText() const override;
 | 
				
			||||||
	bool allowsForward() const override;
 | 
						bool allowsForward() const override;
 | 
				
			||||||
	bool allowsRevoke() const override;
 | 
						bool allowsRevoke() const override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -302,7 +302,7 @@ public:
 | 
				
			||||||
	QString chatListText() const override;
 | 
						QString chatListText() const override;
 | 
				
			||||||
	QString notificationText() const override;
 | 
						QString notificationText() const override;
 | 
				
			||||||
	QString pinnedTextSubstring() const override;
 | 
						QString pinnedTextSubstring() const override;
 | 
				
			||||||
	TextWithEntities clipboardText() const override;
 | 
						TextForMimeData clipboardText() const override;
 | 
				
			||||||
	bool allowsEdit() const override;
 | 
						bool allowsEdit() const override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	bool updateInlineResultMedia(const MTPMessageMedia &media) override;
 | 
						bool updateInlineResultMedia(const MTPMessageMedia &media) override;
 | 
				
			||||||
| 
						 | 
					@ -330,7 +330,7 @@ public:
 | 
				
			||||||
	Image *replyPreview() const override;
 | 
						Image *replyPreview() const override;
 | 
				
			||||||
	QString notificationText() const override;
 | 
						QString notificationText() const override;
 | 
				
			||||||
	QString pinnedTextSubstring() const override;
 | 
						QString pinnedTextSubstring() const override;
 | 
				
			||||||
	TextWithEntities clipboardText() const override;
 | 
						TextForMimeData clipboardText() const override;
 | 
				
			||||||
	QString errorTextForForward(not_null<PeerData*> peer) const override;
 | 
						QString errorTextForForward(not_null<PeerData*> peer) const override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	bool consumeMessageText(const TextWithEntities &text) override;
 | 
						bool consumeMessageText(const TextWithEntities &text) override;
 | 
				
			||||||
| 
						 | 
					@ -365,7 +365,7 @@ public:
 | 
				
			||||||
	Image *replyPreview() const override;
 | 
						Image *replyPreview() const override;
 | 
				
			||||||
	QString notificationText() const override;
 | 
						QString notificationText() const override;
 | 
				
			||||||
	QString pinnedTextSubstring() const override;
 | 
						QString pinnedTextSubstring() const override;
 | 
				
			||||||
	TextWithEntities clipboardText() const override;
 | 
						TextForMimeData clipboardText() const override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	bool updateInlineResultMedia(const MTPMessageMedia &media) override;
 | 
						bool updateInlineResultMedia(const MTPMessageMedia &media) override;
 | 
				
			||||||
	bool updateSentMedia(const MTPMessageMedia &media) override;
 | 
						bool updateSentMedia(const MTPMessageMedia &media) override;
 | 
				
			||||||
| 
						 | 
					@ -391,7 +391,7 @@ public:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	QString notificationText() const override;
 | 
						QString notificationText() const override;
 | 
				
			||||||
	QString pinnedTextSubstring() const override;
 | 
						QString pinnedTextSubstring() const override;
 | 
				
			||||||
	TextWithEntities clipboardText() const override;
 | 
						TextForMimeData clipboardText() const override;
 | 
				
			||||||
	QString errorTextForForward(not_null<PeerData*> peer) const override;
 | 
						QString errorTextForForward(not_null<PeerData*> peer) const override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	bool updateInlineResultMedia(const MTPMessageMedia &media) override;
 | 
						bool updateInlineResultMedia(const MTPMessageMedia &media) override;
 | 
				
			||||||
| 
						 | 
					@ -405,8 +405,8 @@ private:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
TextWithEntities WithCaptionClipboardText(
 | 
					TextForMimeData WithCaptionClipboardText(
 | 
				
			||||||
	const QString &attachType,
 | 
						const QString &attachType,
 | 
				
			||||||
	TextWithEntities &&caption);
 | 
						TextForMimeData &&caption);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
} // namespace Data
 | 
					} // namespace Data
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -253,11 +253,11 @@ void SettingsWidget::addLocationLabel(
 | 
				
			||||||
			QDir::toNativeSeparators(text),
 | 
								QDir::toNativeSeparators(text),
 | 
				
			||||||
			EntitiesInText()
 | 
								EntitiesInText()
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
		pathLink.entities.push_back(EntityInText(
 | 
							pathLink.entities.push_back({
 | 
				
			||||||
			EntityInTextCustomUrl,
 | 
								EntityType::CustomUrl,
 | 
				
			||||||
			0,
 | 
								0,
 | 
				
			||||||
			text.size(),
 | 
								text.size(),
 | 
				
			||||||
			QString("internal:edit_export_path")));
 | 
								QString("internal:edit_export_path") });
 | 
				
			||||||
		return lng_export_option_location__generic<TextWithEntities>(
 | 
							return lng_export_option_location__generic<TextWithEntities>(
 | 
				
			||||||
			lt_path,
 | 
								lt_path,
 | 
				
			||||||
			pathLink);
 | 
								pathLink);
 | 
				
			||||||
| 
						 | 
					@ -288,17 +288,17 @@ void SettingsWidget::addLimitsLabel(
 | 
				
			||||||
			? langDayOfMonthFull(ParseDateTime(till).date())
 | 
								? langDayOfMonthFull(ParseDateTime(till).date())
 | 
				
			||||||
			: lang(lng_export_end);
 | 
								: lang(lng_export_end);
 | 
				
			||||||
		auto fromLink = TextWithEntities{ begin };
 | 
							auto fromLink = TextWithEntities{ begin };
 | 
				
			||||||
		fromLink.entities.push_back(EntityInText(
 | 
							fromLink.entities.push_back({
 | 
				
			||||||
			EntityInTextCustomUrl,
 | 
								EntityType::CustomUrl,
 | 
				
			||||||
			0,
 | 
								0,
 | 
				
			||||||
			begin.size(),
 | 
								begin.size(),
 | 
				
			||||||
			QString("internal:edit_from")));
 | 
								QString("internal:edit_from") });
 | 
				
			||||||
		auto tillLink = TextWithEntities{ end };
 | 
							auto tillLink = TextWithEntities{ end };
 | 
				
			||||||
		tillLink.entities.push_back(EntityInText(
 | 
							tillLink.entities.push_back({
 | 
				
			||||||
			EntityInTextCustomUrl,
 | 
								EntityType::CustomUrl,
 | 
				
			||||||
			0,
 | 
								0,
 | 
				
			||||||
			end.size(),
 | 
								end.size(),
 | 
				
			||||||
			QString("internal:edit_till")));
 | 
								QString("internal:edit_till") });
 | 
				
			||||||
		return lng_export_limits__generic<TextWithEntities>(
 | 
							return lng_export_limits__generic<TextWithEntities>(
 | 
				
			||||||
			lt_from,
 | 
								lt_from,
 | 
				
			||||||
			fromLink,
 | 
								fromLink,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -451,7 +451,7 @@ void InnerWidget::updateEmptyText() {
 | 
				
			||||||
	auto hasSearch = !_searchQuery.isEmpty();
 | 
						auto hasSearch = !_searchQuery.isEmpty();
 | 
				
			||||||
	auto hasFilter = (_filter.flags != 0) || !_filter.allUsers;
 | 
						auto hasFilter = (_filter.flags != 0) || !_filter.allUsers;
 | 
				
			||||||
	auto text = TextWithEntities { lang((hasSearch || hasFilter) ? lng_admin_log_no_results_title : lng_admin_log_no_events_title) };
 | 
						auto text = TextWithEntities { lang((hasSearch || hasFilter) ? lng_admin_log_no_results_title : lng_admin_log_no_events_title) };
 | 
				
			||||||
	text.entities.append(EntityInText(EntityInTextBold, 0, text.text.size()));
 | 
						text.entities.append(EntityInText(EntityType::Bold, 0, text.text.size()));
 | 
				
			||||||
	auto description = hasSearch
 | 
						auto description = hasSearch
 | 
				
			||||||
		? lng_admin_log_no_results_search_text(lt_query, TextUtilities::Clean(_searchQuery))
 | 
							? lng_admin_log_no_results_search_text(lt_query, TextUtilities::Clean(_searchQuery))
 | 
				
			||||||
		: lang(hasFilter ? lng_admin_log_no_results_text : lng_admin_log_no_events_text);
 | 
							: lang(hasFilter ? lng_admin_log_no_results_text : lng_admin_log_no_events_text);
 | 
				
			||||||
| 
						 | 
					@ -890,10 +890,10 @@ void InnerWidget::paintEmpty(Painter &p) {
 | 
				
			||||||
	_emptyText.draw(p, rect.x() + st::historyAdminLogEmptyPadding.left(), rect.y() + st::historyAdminLogEmptyPadding.top(), innerWidth, style::al_top);
 | 
						_emptyText.draw(p, rect.x() + st::historyAdminLogEmptyPadding.left(), rect.y() + st::historyAdminLogEmptyPadding.top(), innerWidth, style::al_top);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
TextWithEntities InnerWidget::getSelectedText() const {
 | 
					TextForMimeData InnerWidget::getSelectedText() const {
 | 
				
			||||||
	return _selectedItem
 | 
						return _selectedItem
 | 
				
			||||||
		? _selectedItem->selectedText(_selectedText)
 | 
							? _selectedItem->selectedText(_selectedText)
 | 
				
			||||||
		: TextWithEntities();
 | 
							: TextForMimeData();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void InnerWidget::keyPressEvent(QKeyEvent *e) {
 | 
					void InnerWidget::keyPressEvent(QKeyEvent *e) {
 | 
				
			||||||
| 
						 | 
					@ -1105,7 +1105,7 @@ void InnerWidget::copyContextImage(PhotoData *photo) {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void InnerWidget::copySelectedText() {
 | 
					void InnerWidget::copySelectedText() {
 | 
				
			||||||
	SetClipboardWithEntities(getSelectedText());
 | 
						SetClipboardText(getSelectedText());
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void InnerWidget::showStickerPackInfo(not_null<DocumentData*> document) {
 | 
					void InnerWidget::showStickerPackInfo(not_null<DocumentData*> document) {
 | 
				
			||||||
| 
						 | 
					@ -1136,7 +1136,7 @@ void InnerWidget::openContextGif(FullMsgId itemId) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void InnerWidget::copyContextText(FullMsgId itemId) {
 | 
					void InnerWidget::copyContextText(FullMsgId itemId) {
 | 
				
			||||||
	if (const auto item = App::histItemById(itemId)) {
 | 
						if (const auto item = App::histItemById(itemId)) {
 | 
				
			||||||
		SetClipboardWithEntities(HistoryItemText(item));
 | 
							SetClipboardText(HistoryItemText(item));
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -153,7 +153,7 @@ private:
 | 
				
			||||||
	void openContextGif(FullMsgId itemId);
 | 
						void openContextGif(FullMsgId itemId);
 | 
				
			||||||
	void copyContextText(FullMsgId itemId);
 | 
						void copyContextText(FullMsgId itemId);
 | 
				
			||||||
	void copySelectedText();
 | 
						void copySelectedText();
 | 
				
			||||||
	TextWithEntities getSelectedText() const;
 | 
						TextForMimeData getSelectedText() const;
 | 
				
			||||||
	void suggestRestrictUser(not_null<UserData*> user);
 | 
						void suggestRestrictUser(not_null<UserData*> user);
 | 
				
			||||||
	void restrictUser(not_null<UserData*> user, const MTPChatBannedRights &oldRights, const MTPChatBannedRights &newRights);
 | 
						void restrictUser(not_null<UserData*> user, const MTPChatBannedRights &oldRights, const MTPChatBannedRights &newRights);
 | 
				
			||||||
	void restrictUserDone(not_null<UserData*> user, const MTPChatBannedRights &rights);
 | 
						void restrictUserDone(not_null<UserData*> user, const MTPChatBannedRights &rights);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -28,7 +28,10 @@ TextWithEntities PrepareText(const QString &value, const QString &emptyValue) {
 | 
				
			||||||
	if (result.text.isEmpty()) {
 | 
						if (result.text.isEmpty()) {
 | 
				
			||||||
		result.text = emptyValue;
 | 
							result.text = emptyValue;
 | 
				
			||||||
		if (!emptyValue.isEmpty()) {
 | 
							if (!emptyValue.isEmpty()) {
 | 
				
			||||||
			result.entities.push_back(EntityInText(EntityInTextItalic, 0, emptyValue.size()));
 | 
								result.entities.push_back({
 | 
				
			||||||
 | 
									EntityType::Italic,
 | 
				
			||||||
 | 
									0,
 | 
				
			||||||
 | 
									emptyValue.size() });
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		TextUtilities::ParseEntities(result, TextParseLinks | TextParseMentions | TextParseHashtags | TextParseBotCommands);
 | 
							TextUtilities::ParseEntities(result, TextParseLinks | TextParseMentions | TextParseHashtags | TextParseBotCommands);
 | 
				
			||||||
| 
						 | 
					@ -224,17 +227,20 @@ auto GenerateUserString(MTPint userId) {
 | 
				
			||||||
	auto entityData = QString::number(user->id)
 | 
						auto entityData = QString::number(user->id)
 | 
				
			||||||
		+ '.'
 | 
							+ '.'
 | 
				
			||||||
		+ QString::number(user->accessHash());
 | 
							+ QString::number(user->accessHash());
 | 
				
			||||||
	name.entities.push_back(EntityInText(
 | 
						name.entities.push_back({
 | 
				
			||||||
		EntityInTextMentionName,
 | 
							EntityType::MentionName,
 | 
				
			||||||
		0,
 | 
							0,
 | 
				
			||||||
		name.text.size(),
 | 
							name.text.size(),
 | 
				
			||||||
		entityData));
 | 
							entityData });
 | 
				
			||||||
	auto username = user->userName();
 | 
						auto username = user->userName();
 | 
				
			||||||
	if (username.isEmpty()) {
 | 
						if (username.isEmpty()) {
 | 
				
			||||||
		return name;
 | 
							return name;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	auto mention = TextWithEntities { '@' + username };
 | 
						auto mention = TextWithEntities { '@' + username };
 | 
				
			||||||
	mention.entities.push_back(EntityInText(EntityInTextMention, 0, mention.text.size()));
 | 
						mention.entities.push_back({
 | 
				
			||||||
 | 
							EntityType::Mention,
 | 
				
			||||||
 | 
							0,
 | 
				
			||||||
 | 
							mention.text.size() });
 | 
				
			||||||
	return lng_admin_log_user_with_username__generic(lt_name, name, lt_mention, mention);
 | 
						return lng_admin_log_user_with_username__generic(lt_name, name, lt_mention, mention);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -285,7 +291,7 @@ auto GenerateParticipantChangeTextInner(
 | 
				
			||||||
 | 
					
 | 
				
			||||||
TextWithEntities GenerateParticipantChangeText(not_null<ChannelData*> channel, const MTPChannelParticipant &participant, const MTPChannelParticipant *oldParticipant = nullptr) {
 | 
					TextWithEntities GenerateParticipantChangeText(not_null<ChannelData*> channel, const MTPChannelParticipant &participant, const MTPChannelParticipant *oldParticipant = nullptr) {
 | 
				
			||||||
	auto result = GenerateParticipantChangeTextInner(channel, participant, oldParticipant);
 | 
						auto result = GenerateParticipantChangeTextInner(channel, participant, oldParticipant);
 | 
				
			||||||
	result.entities.push_front(EntityInText(EntityInTextItalic, 0, result.text.size()));
 | 
						result.entities.push_front(EntityInText(EntityType::Italic, 0, result.text.size()));
 | 
				
			||||||
	return result;
 | 
						return result;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -295,7 +301,7 @@ TextWithEntities GenerateDefaultBannedRightsChangeText(not_null<ChannelData*> ch
 | 
				
			||||||
	if (!changes.isEmpty()) {
 | 
						if (!changes.isEmpty()) {
 | 
				
			||||||
		result.text.append('\n' + changes);
 | 
							result.text.append('\n' + changes);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	result.entities.push_front(EntityInText(EntityInTextItalic, 0, result.text.size()));
 | 
						result.entities.push_front(EntityInText(EntityType::Italic, 0, result.text.size()));
 | 
				
			||||||
	return result;
 | 
						return result;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1156,17 +1156,19 @@ std::unique_ptr<QMimeData> HistoryInner::prepareDrag() {
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	TextWithEntities sel;
 | 
						auto urls = QList<QUrl>();
 | 
				
			||||||
	QList<QUrl> urls;
 | 
						const auto selectedText = [&] {
 | 
				
			||||||
	if (uponSelected) {
 | 
							if (uponSelected) {
 | 
				
			||||||
		sel = getSelectedText();
 | 
								return getSelectedText();
 | 
				
			||||||
	} else if (pressedHandler) {
 | 
							} else if (pressedHandler) {
 | 
				
			||||||
		sel = { pressedHandler->dragText(), EntitiesInText() };
 | 
								//if (!sel.isEmpty() && sel.at(0) != '/' && sel.at(0) != '@' && sel.at(0) != '#') {
 | 
				
			||||||
		//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
 | 
				
			||||||
		//	urls.push_back(QUrl::fromEncoded(sel.toUtf8())); // Google Chrome crashes in Mac OS X O_o
 | 
								//}
 | 
				
			||||||
		//}
 | 
								return TextForMimeData::Simple(pressedHandler->dragText());
 | 
				
			||||||
	}
 | 
							}
 | 
				
			||||||
	if (auto mimeData = MimeDataFromTextWithEntities(sel)) {
 | 
							return TextForMimeData();
 | 
				
			||||||
 | 
						}();
 | 
				
			||||||
 | 
						if (auto mimeData = MimeDataFromText(selectedText)) {
 | 
				
			||||||
		updateDragSelection(nullptr, nullptr, false);
 | 
							updateDragSelection(nullptr, nullptr, false);
 | 
				
			||||||
		_widget->noSelectingScroll();
 | 
							_widget->noSelectingScroll();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1789,7 +1791,7 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void HistoryInner::copySelectedText() {
 | 
					void HistoryInner::copySelectedText() {
 | 
				
			||||||
	SetClipboardWithEntities(getSelectedText());
 | 
						SetClipboardText(getSelectedText());
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void HistoryInner::savePhotoToFile(not_null<PhotoData*> photo) {
 | 
					void HistoryInner::savePhotoToFile(not_null<PhotoData*> photo) {
 | 
				
			||||||
| 
						 | 
					@ -1864,9 +1866,9 @@ void HistoryInner::saveContextGif(FullMsgId itemId) {
 | 
				
			||||||
void HistoryInner::copyContextText(FullMsgId itemId) {
 | 
					void HistoryInner::copyContextText(FullMsgId itemId) {
 | 
				
			||||||
	if (const auto item = App::histItemById(itemId)) {
 | 
						if (const auto item = App::histItemById(itemId)) {
 | 
				
			||||||
		if (const auto group = Auth().data().groups().find(item)) {
 | 
							if (const auto group = Auth().data().groups().find(item)) {
 | 
				
			||||||
			SetClipboardWithEntities(HistoryGroupText(group));
 | 
								SetClipboardText(HistoryGroupText(group));
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
			SetClipboardWithEntities(HistoryItemText(item));
 | 
								SetClipboardText(HistoryItemText(item));
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -1875,7 +1877,7 @@ void HistoryInner::resizeEvent(QResizeEvent *e) {
 | 
				
			||||||
	mouseActionUpdate();
 | 
						mouseActionUpdate();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
TextWithEntities HistoryInner::getSelectedText() const {
 | 
					TextForMimeData HistoryInner::getSelectedText() const {
 | 
				
			||||||
	auto selected = _selected;
 | 
						auto selected = _selected;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (_mouseAction == MouseAction::Selecting && _dragSelFrom && _dragSelTo) {
 | 
						if (_mouseAction == MouseAction::Selecting && _dragSelFrom && _dragSelTo) {
 | 
				
			||||||
| 
						 | 
					@ -1883,32 +1885,32 @@ TextWithEntities HistoryInner::getSelectedText() const {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (selected.empty()) {
 | 
						if (selected.empty()) {
 | 
				
			||||||
		return TextWithEntities();
 | 
							return TextForMimeData();
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if (selected.cbegin()->second != FullSelection) {
 | 
						if (selected.cbegin()->second != FullSelection) {
 | 
				
			||||||
		const auto [item, selection] = *selected.cbegin();
 | 
							const auto [item, selection] = *selected.cbegin();
 | 
				
			||||||
		if (const auto view = item->mainView()) {
 | 
							if (const auto view = item->mainView()) {
 | 
				
			||||||
			return view->selectedText(selection);
 | 
								return view->selectedText(selection);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		return TextWithEntities();
 | 
							return TextForMimeData();
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const auto timeFormat = qsl(", [dd.MM.yy hh:mm]\n");
 | 
						const auto timeFormat = qsl(", [dd.MM.yy hh:mm]\n");
 | 
				
			||||||
	auto groups = base::flat_set<not_null<const Data::Group*>>();
 | 
						auto groups = base::flat_set<not_null<const Data::Group*>>();
 | 
				
			||||||
	auto fullSize = 0;
 | 
						auto fullSize = 0;
 | 
				
			||||||
	auto texts = base::flat_map<Data::MessagePosition, TextWithEntities>();
 | 
						auto texts = base::flat_map<Data::MessagePosition, TextForMimeData>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const auto wrapItem = [&](
 | 
						const auto wrapItem = [&](
 | 
				
			||||||
			not_null<HistoryItem*> item,
 | 
								not_null<HistoryItem*> item,
 | 
				
			||||||
			TextWithEntities &&unwrapped) {
 | 
								TextForMimeData &&unwrapped) {
 | 
				
			||||||
		auto time = ItemDateTime(item).toString(timeFormat);
 | 
							auto time = ItemDateTime(item).toString(timeFormat);
 | 
				
			||||||
		auto part = TextWithEntities();
 | 
							auto part = TextForMimeData();
 | 
				
			||||||
		auto size = item->author()->name.size()
 | 
							auto size = item->author()->name.size()
 | 
				
			||||||
			+ time.size()
 | 
								+ time.size()
 | 
				
			||||||
			+ unwrapped.text.size();
 | 
								+ unwrapped.expanded.size();
 | 
				
			||||||
		part.text.reserve(size);
 | 
							part.reserve(size);
 | 
				
			||||||
		part.text.append(item->author()->name).append(time);
 | 
							part.append(item->author()->name).append(time);
 | 
				
			||||||
		TextUtilities::Append(part, std::move(unwrapped));
 | 
							part.append(std::move(unwrapped));
 | 
				
			||||||
		texts.emplace(item->position(), part);
 | 
							texts.emplace(item->position(), part);
 | 
				
			||||||
		fullSize += size;
 | 
							fullSize += size;
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
| 
						 | 
					@ -1937,13 +1939,13 @@ TextWithEntities HistoryInner::getSelectedText() const {
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	auto result = TextWithEntities();
 | 
						auto result = TextForMimeData();
 | 
				
			||||||
	auto sep = qsl("\n\n");
 | 
						const auto sep = qstr("\n\n");
 | 
				
			||||||
	result.text.reserve(fullSize + (texts.size() - 1) * sep.size());
 | 
						result.reserve(fullSize + (texts.size() - 1) * sep.size());
 | 
				
			||||||
	for (auto i = texts.begin(), e = texts.end(); i != e;) {
 | 
						for (auto i = texts.begin(), e = texts.end(); i != e;) {
 | 
				
			||||||
		TextUtilities::Append(result, std::move(i->second));
 | 
							result.append(std::move(i->second));
 | 
				
			||||||
		if (++i != e) {
 | 
							if (++i != e) {
 | 
				
			||||||
			result.text.append(sep);
 | 
								result.append(sep);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return result;
 | 
						return result;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -55,7 +55,7 @@ public:
 | 
				
			||||||
	void messagesReceived(PeerData *peer, const QVector<MTPMessage> &messages);
 | 
						void messagesReceived(PeerData *peer, const QVector<MTPMessage> &messages);
 | 
				
			||||||
	void messagesReceivedDown(PeerData *peer, const QVector<MTPMessage> &messages);
 | 
						void messagesReceivedDown(PeerData *peer, const QVector<MTPMessage> &messages);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	TextWithEntities getSelectedText() const;
 | 
						TextForMimeData getSelectedText() const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	void touchScrollUpdated(const QPoint &screenPos);
 | 
						void touchScrollUpdated(const QPoint &screenPos);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -66,7 +66,7 @@ not_null<HistoryItem*> CreateUnsupportedMessage(
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
	TextUtilities::ParseEntities(text, Ui::ItemTextNoMonoOptions().flags);
 | 
						TextUtilities::ParseEntities(text, Ui::ItemTextNoMonoOptions().flags);
 | 
				
			||||||
	text.entities.push_front(
 | 
						text.entities.push_front(
 | 
				
			||||||
		EntityInText(EntityInTextItalic, 0, text.text.size()));
 | 
							EntityInText(EntityType::Italic, 0, text.text.size()));
 | 
				
			||||||
	flags &= ~MTPDmessage::Flag::f_post_author;
 | 
						flags &= ~MTPDmessage::Flag::f_post_author;
 | 
				
			||||||
	flags |= MTPDmessage_ClientFlag::f_is_unsupported;
 | 
						flags |= MTPDmessage_ClientFlag::f_is_unsupported;
 | 
				
			||||||
	return new HistoryMessage(
 | 
						return new HistoryMessage(
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -220,10 +220,10 @@ public:
 | 
				
			||||||
		return inDialogsText(DrawInDialog::WithoutSender);
 | 
							return inDialogsText(DrawInDialog::WithoutSender);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	virtual TextWithEntities originalText() const {
 | 
						virtual TextWithEntities originalText() const {
 | 
				
			||||||
		return { QString(), EntitiesInText() };
 | 
							return TextWithEntities();
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	virtual TextWithEntities clipboardText() const {
 | 
						virtual TextForMimeData clipboardText() const {
 | 
				
			||||||
		return { QString(), EntitiesInText() };
 | 
							return TextForMimeData();
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	virtual void setViewsCount(int32 count) {
 | 
						virtual void setViewsCount(int32 count) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -16,50 +16,41 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 | 
				
			||||||
#include "lang/lang_keys.h"
 | 
					#include "lang/lang_keys.h"
 | 
				
			||||||
#include "ui/text_options.h"
 | 
					#include "ui/text_options.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
TextWithEntities WrapAsReply(
 | 
					TextForMimeData WrapAsReply(
 | 
				
			||||||
		TextWithEntities &&text,
 | 
							TextForMimeData &&text,
 | 
				
			||||||
		not_null<HistoryItem*> to) {
 | 
							not_null<HistoryItem*> to) {
 | 
				
			||||||
	const auto name = to->author()->name;
 | 
						const auto name = to->author()->name;
 | 
				
			||||||
	auto result = TextWithEntities();
 | 
						auto result = TextForMimeData();
 | 
				
			||||||
	result.text.reserve(
 | 
						result.reserve(
 | 
				
			||||||
		lang(lng_in_reply_to).size()
 | 
							lang(lng_in_reply_to).size()
 | 
				
			||||||
		+ name.size()
 | 
							+ name.size()
 | 
				
			||||||
		+ 4
 | 
							+ 4
 | 
				
			||||||
		+ text.text.size());
 | 
							+ text.expanded.size());
 | 
				
			||||||
	result.text.append('['
 | 
						return result.append('['
 | 
				
			||||||
	).append(lang(lng_in_reply_to)
 | 
						).append(lang(lng_in_reply_to)
 | 
				
			||||||
	).append(' '
 | 
						).append(' '
 | 
				
			||||||
	).append(name
 | 
						).append(name
 | 
				
			||||||
	).append(qsl("]\n")
 | 
						).append(qstr("]\n")
 | 
				
			||||||
	);
 | 
						).append(std::move(text));
 | 
				
			||||||
	TextUtilities::Append(result, std::move(text));
 | 
					 | 
				
			||||||
	return result;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
TextWithEntities WrapAsForwarded(
 | 
					TextForMimeData WrapAsForwarded(
 | 
				
			||||||
		TextWithEntities &&text,
 | 
							TextForMimeData &&text,
 | 
				
			||||||
		not_null<HistoryMessageForwarded*> forwarded) {
 | 
							not_null<HistoryMessageForwarded*> forwarded) {
 | 
				
			||||||
	auto info = forwarded->text.toTextWithEntities(
 | 
						auto info = forwarded->text.toTextForMimeData();
 | 
				
			||||||
		AllTextSelection,
 | 
						auto result = TextForMimeData();
 | 
				
			||||||
		ExpandLinksAll);
 | 
						result.reserve(
 | 
				
			||||||
	auto result = TextWithEntities();
 | 
							info.expanded.size() + 4 + text.expanded.size(),
 | 
				
			||||||
	result.text.reserve(
 | 
							info.rich.entities.size() + text.rich.entities.size());
 | 
				
			||||||
		info.text.size()
 | 
						return result.append('['
 | 
				
			||||||
		+ 4
 | 
						).append(std::move(info)
 | 
				
			||||||
		+ text.text.size());
 | 
						).append(qstr("]\n")
 | 
				
			||||||
	result.entities.reserve(
 | 
						).append(std::move(text));
 | 
				
			||||||
		info.entities.size()
 | 
					 | 
				
			||||||
		+ text.entities.size());
 | 
					 | 
				
			||||||
	result.text.append('[');
 | 
					 | 
				
			||||||
	TextUtilities::Append(result, std::move(info));
 | 
					 | 
				
			||||||
	result.text.append(qsl("]\n"));
 | 
					 | 
				
			||||||
	TextUtilities::Append(result, std::move(text));
 | 
					 | 
				
			||||||
	return result;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
TextWithEntities WrapAsItem(
 | 
					TextForMimeData WrapAsItem(
 | 
				
			||||||
		not_null<HistoryItem*> item,
 | 
							not_null<HistoryItem*> item,
 | 
				
			||||||
		TextWithEntities &&result) {
 | 
							TextForMimeData &&result) {
 | 
				
			||||||
	if (const auto reply = item->Get<HistoryMessageReply>()) {
 | 
						if (const auto reply = item->Get<HistoryMessageReply>()) {
 | 
				
			||||||
		if (const auto message = reply->replyToMsg) {
 | 
							if (const auto message = reply->replyToMsg) {
 | 
				
			||||||
			result = WrapAsReply(std::move(result), message);
 | 
								result = WrapAsReply(std::move(result), message);
 | 
				
			||||||
| 
						 | 
					@ -71,65 +62,65 @@ TextWithEntities WrapAsItem(
 | 
				
			||||||
	return std::move(result);
 | 
						return std::move(result);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
TextWithEntities HistoryItemText(not_null<HistoryItem*> item) {
 | 
					TextForMimeData HistoryItemText(not_null<HistoryItem*> item) {
 | 
				
			||||||
	const auto media = item->media();
 | 
						const auto media = item->media();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	auto mediaResult = media ? media->clipboardText() : TextWithEntities();
 | 
						auto mediaResult = media ? media->clipboardText() : TextForMimeData();
 | 
				
			||||||
	auto textResult = mediaResult.text.isEmpty()
 | 
						auto textResult = mediaResult.empty()
 | 
				
			||||||
		? item->clipboardText()
 | 
							? item->clipboardText()
 | 
				
			||||||
		: TextWithEntities();
 | 
							: TextForMimeData();
 | 
				
			||||||
	auto logEntryOriginalResult = [&] {
 | 
						auto logEntryOriginalResult = [&] {
 | 
				
			||||||
		const auto entry = item->Get<HistoryMessageLogEntryOriginal>();
 | 
							const auto entry = item->Get<HistoryMessageLogEntryOriginal>();
 | 
				
			||||||
		if (!entry) {
 | 
							if (!entry) {
 | 
				
			||||||
			return TextWithEntities();
 | 
								return TextForMimeData();
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		const auto title = TextUtilities::SingleLine(entry->page->title.isEmpty()
 | 
							const auto title = TextUtilities::SingleLine(entry->page->title.isEmpty()
 | 
				
			||||||
			? entry->page->author
 | 
								? entry->page->author
 | 
				
			||||||
			: entry->page->title);
 | 
								: entry->page->title);
 | 
				
			||||||
		auto titleResult = TextUtilities::ParseEntities(
 | 
							auto titleResult = TextForMimeData::Rich(
 | 
				
			||||||
			title,
 | 
								TextUtilities::ParseEntities(
 | 
				
			||||||
			Ui::WebpageTextTitleOptions().flags);
 | 
									title,
 | 
				
			||||||
		auto descriptionResult = entry->page->description;
 | 
									Ui::WebpageTextTitleOptions().flags));
 | 
				
			||||||
		if (titleResult.text.isEmpty()) {
 | 
							auto descriptionResult = TextForMimeData::Rich(
 | 
				
			||||||
 | 
								base::duplicate(entry->page->description));
 | 
				
			||||||
 | 
							if (titleResult.empty()) {
 | 
				
			||||||
			return descriptionResult;
 | 
								return descriptionResult;
 | 
				
			||||||
		} else if (descriptionResult.text.isEmpty()) {
 | 
							} else if (descriptionResult.empty()) {
 | 
				
			||||||
			return titleResult;
 | 
								return titleResult;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		titleResult.text += '\n';
 | 
							titleResult.append('\n').append(std::move(descriptionResult));
 | 
				
			||||||
		TextUtilities::Append(titleResult, std::move(descriptionResult));
 | 
					 | 
				
			||||||
		return titleResult;
 | 
							return titleResult;
 | 
				
			||||||
	}();
 | 
						}();
 | 
				
			||||||
	auto result = textResult;
 | 
						auto result = textResult;
 | 
				
			||||||
	if (result.text.isEmpty()) {
 | 
						if (result.empty()) {
 | 
				
			||||||
		result = std::move(mediaResult);
 | 
							result = std::move(mediaResult);
 | 
				
			||||||
	} else if (!mediaResult.text.isEmpty()) {
 | 
						} else if (!mediaResult.empty()) {
 | 
				
			||||||
		result.text += qstr("\n\n");
 | 
							result.append(qstr("\n\n")).append(std::move(mediaResult));
 | 
				
			||||||
		TextUtilities::Append(result, std::move(mediaResult));
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if (result.text.isEmpty()) {
 | 
						if (result.empty()) {
 | 
				
			||||||
		result = std::move(logEntryOriginalResult);
 | 
							result = std::move(logEntryOriginalResult);
 | 
				
			||||||
	} else if (!logEntryOriginalResult.text.isEmpty()) {
 | 
						} else if (!logEntryOriginalResult.empty()) {
 | 
				
			||||||
		result.text += qstr("\n\n");
 | 
							result.append(qstr("\n\n")).append(std::move(logEntryOriginalResult));
 | 
				
			||||||
		TextUtilities::Append(result, std::move(logEntryOriginalResult));
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return WrapAsItem(item, std::move(result));
 | 
						return WrapAsItem(item, std::move(result));
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
TextWithEntities HistoryGroupText(not_null<const Data::Group*> group) {
 | 
					TextForMimeData HistoryGroupText(not_null<const Data::Group*> group) {
 | 
				
			||||||
	Expects(!group->items.empty());
 | 
						Expects(!group->items.empty());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	auto caption = [&] {
 | 
						auto caption = [&] {
 | 
				
			||||||
		const auto first = begin(group->items);
 | 
							auto &&nonempty = ranges::view::all(
 | 
				
			||||||
		const auto result = (*first)->clipboardText();
 | 
								group->items
 | 
				
			||||||
		if (result.text.isEmpty()) {
 | 
							) | ranges::view::filter([](not_null<HistoryItem*> item) {
 | 
				
			||||||
			return result;
 | 
								return !item->clipboardText().empty();
 | 
				
			||||||
 | 
							}) | ranges::view::take(2);
 | 
				
			||||||
 | 
							auto first = nonempty.begin();
 | 
				
			||||||
 | 
							auto end = nonempty.end();
 | 
				
			||||||
 | 
							if (first == end) {
 | 
				
			||||||
 | 
								return TextForMimeData();
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		for (auto i = first + 1; i != end(group->items); ++i) {
 | 
							auto result = (*first)->clipboardText();
 | 
				
			||||||
			if (!(*i)->clipboardText().text.isEmpty()) {
 | 
							return (++first == end) ? result : TextForMimeData();
 | 
				
			||||||
				return TextWithEntities();
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		return result;
 | 
					 | 
				
			||||||
	}();
 | 
						}();
 | 
				
			||||||
	return WrapAsItem(group->items.back(), Data::WithCaptionClipboardText(
 | 
						return WrapAsItem(group->items.back(), Data::WithCaptionClipboardText(
 | 
				
			||||||
		lang(lng_in_dlg_album),
 | 
							lang(lng_in_dlg_album),
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -13,5 +13,5 @@ namespace Data {
 | 
				
			||||||
struct Group;
 | 
					struct Group;
 | 
				
			||||||
} // namespace Data
 | 
					} // namespace Data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
TextWithEntities HistoryItemText(not_null<HistoryItem*> item);
 | 
					TextForMimeData HistoryItemText(not_null<HistoryItem*> item);
 | 
				
			||||||
TextWithEntities HistoryGroupText(not_null<const Data::Group*> group);
 | 
					TextForMimeData HistoryGroupText(not_null<const Data::Group*> group);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1009,9 +1009,9 @@ Storage::SharedMediaTypesMask HistoryMessage::sharedMediaTypes() const {
 | 
				
			||||||
void HistoryMessage::setText(const TextWithEntities &textWithEntities) {
 | 
					void HistoryMessage::setText(const TextWithEntities &textWithEntities) {
 | 
				
			||||||
	for_const (auto &entity, textWithEntities.entities) {
 | 
						for_const (auto &entity, textWithEntities.entities) {
 | 
				
			||||||
		auto type = entity.type();
 | 
							auto type = entity.type();
 | 
				
			||||||
		if (type == EntityInTextUrl
 | 
							if (type == EntityType::Url
 | 
				
			||||||
			|| type == EntityInTextCustomUrl
 | 
								|| type == EntityType::CustomUrl
 | 
				
			||||||
			|| type == EntityInTextEmail) {
 | 
								|| type == EntityType::Email) {
 | 
				
			||||||
			_flags |= MTPDmessage_ClientFlag::f_has_text_links;
 | 
								_flags |= MTPDmessage_ClientFlag::f_has_text_links;
 | 
				
			||||||
			break;
 | 
								break;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
| 
						 | 
					@ -1096,11 +1096,11 @@ TextWithEntities HistoryMessage::originalText() const {
 | 
				
			||||||
	return _text.toTextWithEntities();
 | 
						return _text.toTextWithEntities();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
TextWithEntities HistoryMessage::clipboardText() const {
 | 
					TextForMimeData HistoryMessage::clipboardText() const {
 | 
				
			||||||
	if (emptyText()) {
 | 
						if (emptyText()) {
 | 
				
			||||||
		return { QString(), EntitiesInText() };
 | 
							return TextForMimeData();
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return _text.toTextWithEntities(AllTextSelection, ExpandLinksAll);
 | 
						return _text.toTextForMimeData();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bool HistoryMessage::textHasLinks() const {
 | 
					bool HistoryMessage::textHasLinks() const {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -123,7 +123,7 @@ public:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	void setText(const TextWithEntities &textWithEntities) override;
 | 
						void setText(const TextWithEntities &textWithEntities) override;
 | 
				
			||||||
	TextWithEntities originalText() const override;
 | 
						TextWithEntities originalText() const override;
 | 
				
			||||||
	TextWithEntities clipboardText() const override;
 | 
						TextForMimeData clipboardText() const override;
 | 
				
			||||||
	bool textHasLinks() const override;
 | 
						bool textHasLinks() const override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	int viewsCount() const override;
 | 
						int viewsCount() const override;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -49,9 +49,9 @@ public:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	[[nodiscard]] not_null<History*> history() const;
 | 
						[[nodiscard]] not_null<History*> history() const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	[[nodiscard]] virtual TextWithEntities selectedText(
 | 
						[[nodiscard]] virtual TextForMimeData selectedText(
 | 
				
			||||||
			TextSelection selection) const {
 | 
								TextSelection selection) const {
 | 
				
			||||||
		return TextWithEntities();
 | 
							return TextForMimeData();
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	[[nodiscard]] virtual bool isDisplayed() const;
 | 
						[[nodiscard]] virtual bool isDisplayed() const;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -651,12 +651,12 @@ bool HistoryDocument::hasTextForCopy() const {
 | 
				
			||||||
	return Has<HistoryDocumentCaptioned>();
 | 
						return Has<HistoryDocumentCaptioned>();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
TextWithEntities HistoryDocument::selectedText(TextSelection selection) const {
 | 
					TextForMimeData HistoryDocument::selectedText(TextSelection selection) const {
 | 
				
			||||||
	if (const auto captioned = Get<HistoryDocumentCaptioned>()) {
 | 
						if (const auto captioned = Get<HistoryDocumentCaptioned>()) {
 | 
				
			||||||
		const auto &caption = captioned->_caption;
 | 
							const auto &caption = captioned->_caption;
 | 
				
			||||||
		return caption.toTextWithEntities(selection, ExpandLinksAll);
 | 
							return captioned->_caption.toTextForMimeData(selection);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return TextWithEntities();
 | 
						return TextForMimeData();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bool HistoryDocument::uploading() const {
 | 
					bool HistoryDocument::uploading() const {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -30,7 +30,7 @@ public:
 | 
				
			||||||
	uint16 fullSelectionLength() const override;
 | 
						uint16 fullSelectionLength() const override;
 | 
				
			||||||
	bool hasTextForCopy() const override;
 | 
						bool hasTextForCopy() const override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	TextWithEntities selectedText(TextSelection selection) const override;
 | 
						TextForMimeData selectedText(TextSelection selection) const override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	bool uploading() const override;
 | 
						bool uploading() const override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -367,22 +367,16 @@ void HistoryGame::clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pres
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
TextWithEntities HistoryGame::selectedText(TextSelection selection) const {
 | 
					TextForMimeData HistoryGame::selectedText(TextSelection selection) const {
 | 
				
			||||||
	auto titleResult = _title.toTextWithEntities(
 | 
						auto titleResult = _title.toTextForMimeData(selection);
 | 
				
			||||||
		selection,
 | 
						auto descriptionResult = _description.toTextForMimeData(
 | 
				
			||||||
		ExpandLinksAll);
 | 
							toDescriptionSelection(selection));
 | 
				
			||||||
	auto descriptionResult = _description.toTextWithEntities(
 | 
						if (titleResult.empty()) {
 | 
				
			||||||
		toDescriptionSelection(selection),
 | 
					 | 
				
			||||||
		ExpandLinksAll);
 | 
					 | 
				
			||||||
	if (titleResult.text.isEmpty()) {
 | 
					 | 
				
			||||||
		return descriptionResult;
 | 
							return descriptionResult;
 | 
				
			||||||
	} else if (descriptionResult.text.isEmpty()) {
 | 
						} else if (descriptionResult.empty()) {
 | 
				
			||||||
		return titleResult;
 | 
							return titleResult;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						return titleResult.append('\n').append(std::move(descriptionResult));
 | 
				
			||||||
	titleResult.text += '\n';
 | 
					 | 
				
			||||||
	TextUtilities::Append(titleResult, std::move(descriptionResult));
 | 
					 | 
				
			||||||
	return titleResult;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void HistoryGame::playAnimation(bool autoplay) {
 | 
					void HistoryGame::playAnimation(bool autoplay) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -40,7 +40,7 @@ public:
 | 
				
			||||||
		return _attach && _attach->dragItemByHandler(p);
 | 
							return _attach && _attach->dragItemByHandler(p);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	TextWithEntities selectedText(TextSelection selection) const override;
 | 
						TextForMimeData selectedText(TextSelection selection) const override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override;
 | 
						void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override;
 | 
				
			||||||
	void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override;
 | 
						void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -680,8 +680,8 @@ TextState HistoryGif::textState(QPoint point, StateRequest request) const {
 | 
				
			||||||
	return result;
 | 
						return result;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
TextWithEntities HistoryGif::selectedText(TextSelection selection) const {
 | 
					TextForMimeData HistoryGif::selectedText(TextSelection selection) const {
 | 
				
			||||||
	return _caption.toTextWithEntities(selection, ExpandLinksAll);
 | 
						return _caption.toTextForMimeData(selection);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bool HistoryGif::uploading() const {
 | 
					bool HistoryGif::uploading() const {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -46,7 +46,7 @@ public:
 | 
				
			||||||
		return !_caption.isEmpty();
 | 
							return !_caption.isEmpty();
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	TextWithEntities selectedText(TextSelection selection) const override;
 | 
						TextForMimeData selectedText(TextSelection selection) const override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	bool uploading() const override;
 | 
						bool uploading() const override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -304,9 +304,9 @@ TextSelection HistoryGroupedMedia::adjustSelection(
 | 
				
			||||||
	return _caption.adjustSelection(selection, type);
 | 
						return _caption.adjustSelection(selection, type);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
TextWithEntities HistoryGroupedMedia::selectedText(
 | 
					TextForMimeData HistoryGroupedMedia::selectedText(
 | 
				
			||||||
		TextSelection selection) const {
 | 
							TextSelection selection) const {
 | 
				
			||||||
	return _caption.toTextWithEntities(selection, ExpandLinksAll);
 | 
						return _caption.toTextForMimeData(selection);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void HistoryGroupedMedia::clickHandlerActiveChanged(
 | 
					void HistoryGroupedMedia::clickHandlerActiveChanged(
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -55,7 +55,7 @@ public:
 | 
				
			||||||
	PhotoData *getPhoto() const override;
 | 
						PhotoData *getPhoto() const override;
 | 
				
			||||||
	DocumentData *getDocument() const override;
 | 
						DocumentData *getDocument() const override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	TextWithEntities selectedText(TextSelection selection) const override;
 | 
						TextForMimeData selectedText(TextSelection selection) const override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	void clickHandlerActiveChanged(
 | 
						void clickHandlerActiveChanged(
 | 
				
			||||||
		const ClickHandlerPtr &p,
 | 
							const ClickHandlerPtr &p,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -57,7 +57,10 @@ void HistoryInvoice::fillFromData(not_null<Data::Invoice*> invoice) {
 | 
				
			||||||
		FillAmountAndCurrency(invoice->amount, invoice->currency),
 | 
							FillAmountAndCurrency(invoice->amount, invoice->currency),
 | 
				
			||||||
		EntitiesInText()
 | 
							EntitiesInText()
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
	statusText.entities.push_back(EntityInText(EntityInTextBold, 0, statusText.text.size()));
 | 
						statusText.entities.push_back({
 | 
				
			||||||
 | 
							EntityType::Bold,
 | 
				
			||||||
 | 
							0,
 | 
				
			||||||
 | 
							statusText.text.size() });
 | 
				
			||||||
	statusText.text += ' ' + labelText().toUpper();
 | 
						statusText.text += ' ' + labelText().toUpper();
 | 
				
			||||||
	_status.setMarkedText(
 | 
						_status.setMarkedText(
 | 
				
			||||||
		st::defaultTextStyle,
 | 
							st::defaultTextStyle,
 | 
				
			||||||
| 
						 | 
					@ -358,22 +361,16 @@ void HistoryInvoice::clickHandlerPressedChanged(const ClickHandlerPtr &p, bool p
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
TextWithEntities HistoryInvoice::selectedText(TextSelection selection) const {
 | 
					TextForMimeData HistoryInvoice::selectedText(TextSelection selection) const {
 | 
				
			||||||
	auto titleResult = _title.toTextWithEntities(
 | 
						auto titleResult = _title.toTextForMimeData(selection);
 | 
				
			||||||
		selection,
 | 
						auto descriptionResult = _description.toTextForMimeData(
 | 
				
			||||||
		ExpandLinksAll);
 | 
							toDescriptionSelection(selection));
 | 
				
			||||||
	auto descriptionResult = _description.toTextWithEntities(
 | 
						if (titleResult.empty()) {
 | 
				
			||||||
		toDescriptionSelection(selection),
 | 
					 | 
				
			||||||
		ExpandLinksAll);
 | 
					 | 
				
			||||||
	if (titleResult.text.isEmpty()) {
 | 
					 | 
				
			||||||
		return descriptionResult;
 | 
							return descriptionResult;
 | 
				
			||||||
	} else if (descriptionResult.text.isEmpty()) {
 | 
						} else if (descriptionResult.empty()) {
 | 
				
			||||||
		return titleResult;
 | 
							return titleResult;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						return titleResult.append('\n').append(std::move(descriptionResult));
 | 
				
			||||||
	titleResult.text += '\n';
 | 
					 | 
				
			||||||
	TextUtilities::Append(titleResult, std::move(descriptionResult));
 | 
					 | 
				
			||||||
	return titleResult;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
QMargins HistoryInvoice::inBubblePadding() const {
 | 
					QMargins HistoryInvoice::inBubblePadding() const {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -52,7 +52,7 @@ public:
 | 
				
			||||||
		return _attach && _attach->dragItemByHandler(p);
 | 
							return _attach && _attach->dragItemByHandler(p);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	TextWithEntities selectedText(TextSelection selection) const override;
 | 
						TextForMimeData selectedText(TextSelection selection) const override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override;
 | 
						void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override;
 | 
				
			||||||
	void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override;
 | 
						void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -278,17 +278,16 @@ TextSelection HistoryLocation::adjustSelection(TextSelection selection, TextSele
 | 
				
			||||||
	return { titleSelection.from, fromDescriptionSelection(descriptionSelection).to };
 | 
						return { titleSelection.from, fromDescriptionSelection(descriptionSelection).to };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
TextWithEntities HistoryLocation::selectedText(TextSelection selection) const {
 | 
					TextForMimeData HistoryLocation::selectedText(TextSelection selection) const {
 | 
				
			||||||
	auto titleResult = _title.toTextWithEntities(selection);
 | 
						auto titleResult = _title.toTextForMimeData(selection);
 | 
				
			||||||
	auto descriptionResult = _description.toTextWithEntities(toDescriptionSelection(selection));
 | 
						auto descriptionResult = _description.toTextForMimeData(
 | 
				
			||||||
	if (titleResult.text.isEmpty()) {
 | 
							toDescriptionSelection(selection));
 | 
				
			||||||
 | 
						if (titleResult.empty()) {
 | 
				
			||||||
		return descriptionResult;
 | 
							return descriptionResult;
 | 
				
			||||||
	} else if (descriptionResult.text.isEmpty()) {
 | 
						} else if (descriptionResult.empty()) {
 | 
				
			||||||
		return titleResult;
 | 
							return titleResult;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	titleResult.text += '\n';
 | 
						return titleResult.append('\n').append(std::move(descriptionResult));
 | 
				
			||||||
	TextUtilities::Append(titleResult, std::move(descriptionResult));
 | 
					 | 
				
			||||||
	return titleResult;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bool HistoryLocation::needsBubble() const {
 | 
					bool HistoryLocation::needsBubble() const {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -40,7 +40,7 @@ public:
 | 
				
			||||||
		return p == _link;
 | 
							return p == _link;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	TextWithEntities selectedText(TextSelection selection) const override;
 | 
						TextForMimeData selectedText(TextSelection selection) const override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	bool needsBubble() const override;
 | 
						bool needsBubble() const override;
 | 
				
			||||||
	bool customInfoLayout() const override {
 | 
						bool customInfoLayout() const override {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -535,8 +535,8 @@ void HistoryPhoto::validateGroupedCache(
 | 
				
			||||||
	*cache = image->pixNoCache(_realParent->fullId(), pixWidth, pixHeight, options, width, height);
 | 
						*cache = image->pixNoCache(_realParent->fullId(), pixWidth, pixHeight, options, width, height);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
TextWithEntities HistoryPhoto::selectedText(TextSelection selection) const {
 | 
					TextForMimeData HistoryPhoto::selectedText(TextSelection selection) const {
 | 
				
			||||||
	return _caption.toTextWithEntities(selection, ExpandLinksAll);
 | 
						return _caption.toTextForMimeData(selection);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bool HistoryPhoto::needsBubble() const {
 | 
					bool HistoryPhoto::needsBubble() const {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -36,7 +36,7 @@ public:
 | 
				
			||||||
		return !_caption.isEmpty();
 | 
							return !_caption.isEmpty();
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	TextWithEntities selectedText(TextSelection selection) const override;
 | 
						TextForMimeData selectedText(TextSelection selection) const override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	PhotoData *getPhoto() const override {
 | 
						PhotoData *getPhoto() const override {
 | 
				
			||||||
		return _data;
 | 
							return _data;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -576,8 +576,8 @@ void HistoryVideo::setStatusSize(int newSize) const {
 | 
				
			||||||
	HistoryFileMedia::setStatusSize(newSize, _data->size, _data->getDuration(), 0);
 | 
						HistoryFileMedia::setStatusSize(newSize, _data->size, _data->getDuration(), 0);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
TextWithEntities HistoryVideo::selectedText(TextSelection selection) const {
 | 
					TextForMimeData HistoryVideo::selectedText(TextSelection selection) const {
 | 
				
			||||||
	return _caption.toTextWithEntities(selection, ExpandLinksAll);
 | 
						return _caption.toTextForMimeData(selection);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bool HistoryVideo::needsBubble() const {
 | 
					bool HistoryVideo::needsBubble() const {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -31,7 +31,7 @@ public:
 | 
				
			||||||
		return !_caption.isEmpty();
 | 
							return !_caption.isEmpty();
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	TextWithEntities selectedText(TextSelection selection) const override;
 | 
						TextForMimeData selectedText(TextSelection selection) const override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	DocumentData *getDocument() const override {
 | 
						DocumentData *getDocument() const override {
 | 
				
			||||||
		return _data;
 | 
							return _data;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -118,7 +118,7 @@ QSize HistoryWebPage::countOptimalSize() {
 | 
				
			||||||
			const auto simplified = simplify(_data->url);
 | 
								const auto simplified = simplify(_data->url);
 | 
				
			||||||
			const auto full = _parent->data()->originalText();
 | 
								const auto full = _parent->data()->originalText();
 | 
				
			||||||
			for (const auto &entity : full.entities) {
 | 
								for (const auto &entity : full.entities) {
 | 
				
			||||||
				if (entity.type() != EntityInTextUrl) {
 | 
									if (entity.type() != EntityType::Url) {
 | 
				
			||||||
					continue;
 | 
										continue;
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
				const auto link = full.text.mid(
 | 
									const auto link = full.text.mid(
 | 
				
			||||||
| 
						 | 
					@ -685,22 +685,17 @@ bool HistoryWebPage::isDisplayed() const {
 | 
				
			||||||
		&& !item->Has<HistoryMessageLogEntryOriginal>();
 | 
							&& !item->Has<HistoryMessageLogEntryOriginal>();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
TextWithEntities HistoryWebPage::selectedText(TextSelection selection) const {
 | 
					TextForMimeData HistoryWebPage::selectedText(TextSelection selection) const {
 | 
				
			||||||
	auto titleResult = _title.toTextWithEntities(
 | 
						auto titleResult = _title.toTextForMimeData(selection);
 | 
				
			||||||
		selection,
 | 
						auto descriptionResult = _description.toTextForMimeData(
 | 
				
			||||||
		ExpandLinksAll);
 | 
							toDescriptionSelection(selection));
 | 
				
			||||||
	auto descriptionResult = _description.toTextWithEntities(
 | 
						if (titleResult.empty()) {
 | 
				
			||||||
		toDescriptionSelection(selection),
 | 
					 | 
				
			||||||
		ExpandLinksAll);
 | 
					 | 
				
			||||||
	if (titleResult.text.isEmpty()) {
 | 
					 | 
				
			||||||
		return descriptionResult;
 | 
							return descriptionResult;
 | 
				
			||||||
	} else if (descriptionResult.text.isEmpty()) {
 | 
						} else if (descriptionResult.empty()) {
 | 
				
			||||||
		return titleResult;
 | 
							return titleResult;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	titleResult.text += '\n';
 | 
						return titleResult.append('\n').append(std::move(descriptionResult));
 | 
				
			||||||
	TextUtilities::Append(titleResult, std::move(descriptionResult));
 | 
					 | 
				
			||||||
	return titleResult;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
QMargins HistoryWebPage::inBubblePadding() const {
 | 
					QMargins HistoryWebPage::inBubblePadding() const {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -45,7 +45,7 @@ public:
 | 
				
			||||||
		return _attach && _attach->dragItemByHandler(p);
 | 
							return _attach && _attach->dragItemByHandler(p);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	TextWithEntities selectedText(TextSelection selection) const override;
 | 
						TextForMimeData selectedText(TextSelection selection) const override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override;
 | 
						void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override;
 | 
				
			||||||
	void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override;
 | 
						void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -454,14 +454,14 @@ base::unique_qptr<Ui::PopupMenu> FillContextMenu(
 | 
				
			||||||
	const auto isVoiceLink = document ? document->isVoiceMessage() : false;
 | 
						const auto isVoiceLink = document ? document->isVoiceMessage() : false;
 | 
				
			||||||
	const auto isAudioLink = document ? document->isAudioFile() : false;
 | 
						const auto isAudioLink = document ? document->isAudioFile() : false;
 | 
				
			||||||
	const auto hasSelection = !request.selectedItems.empty()
 | 
						const auto hasSelection = !request.selectedItems.empty()
 | 
				
			||||||
		|| !request.selectedText.text.isEmpty();
 | 
							|| !request.selectedText.empty();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (request.overSelection) {
 | 
						if (request.overSelection) {
 | 
				
			||||||
		const auto text = lang(request.selectedItems.empty()
 | 
							const auto text = lang(request.selectedItems.empty()
 | 
				
			||||||
			? lng_context_copy_selected
 | 
								? lng_context_copy_selected
 | 
				
			||||||
			: lng_context_copy_selected_items);
 | 
								: lng_context_copy_selected_items);
 | 
				
			||||||
		result->addAction(text, [=] {
 | 
							result->addAction(text, [=] {
 | 
				
			||||||
			SetClipboardWithEntities(list->getSelectedText());
 | 
								SetClipboardText(list->getSelectedText());
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -493,11 +493,11 @@ base::unique_qptr<Ui::PopupMenu> FillContextMenu(
 | 
				
			||||||
				if (const auto item = App::histItemById(itemId)) {
 | 
									if (const auto item = App::histItemById(itemId)) {
 | 
				
			||||||
					if (asGroup) {
 | 
										if (asGroup) {
 | 
				
			||||||
						if (const auto group = Auth().data().groups().find(item)) {
 | 
											if (const auto group = Auth().data().groups().find(item)) {
 | 
				
			||||||
							SetClipboardWithEntities(HistoryGroupText(group));
 | 
												SetClipboardText(HistoryGroupText(group));
 | 
				
			||||||
							return;
 | 
												return;
 | 
				
			||||||
						}
 | 
											}
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
					SetClipboardWithEntities(HistoryItemText(item));
 | 
										SetClipboardText(HistoryItemText(item));
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			});
 | 
								});
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -26,7 +26,7 @@ struct ContextMenuRequest {
 | 
				
			||||||
	Element *view = nullptr;
 | 
						Element *view = nullptr;
 | 
				
			||||||
	HistoryItem *item = nullptr;
 | 
						HistoryItem *item = nullptr;
 | 
				
			||||||
	SelectedItems selectedItems;
 | 
						SelectedItems selectedItems;
 | 
				
			||||||
	TextWithEntities selectedText;
 | 
						TextForMimeData selectedText;
 | 
				
			||||||
	bool overSelection = false;
 | 
						bool overSelection = false;
 | 
				
			||||||
	PointState pointState = PointState();
 | 
						PointState pointState = PointState();
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -195,7 +195,7 @@ public:
 | 
				
			||||||
		int bottom,
 | 
							int bottom,
 | 
				
			||||||
		QPoint point,
 | 
							QPoint point,
 | 
				
			||||||
		InfoDisplayType type) const;
 | 
							InfoDisplayType type) const;
 | 
				
			||||||
	virtual TextWithEntities selectedText(
 | 
						virtual TextForMimeData selectedText(
 | 
				
			||||||
		TextSelection selection) const = 0;
 | 
							TextSelection selection) const = 0;
 | 
				
			||||||
	[[nodiscard]] virtual TextSelection adjustSelection(
 | 
						[[nodiscard]] virtual TextSelection adjustSelection(
 | 
				
			||||||
		TextSelection selection,
 | 
							TextSelection selection,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -952,7 +952,7 @@ void ListWidget::clearTextSelection() {
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		_selectedTextItem = nullptr;
 | 
							_selectedTextItem = nullptr;
 | 
				
			||||||
		_selectedTextRange = TextSelection();
 | 
							_selectedTextRange = TextSelection();
 | 
				
			||||||
		_selectedText = TextWithEntities();
 | 
							_selectedText = TextForMimeData();
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -968,9 +968,9 @@ void ListWidget::setTextSelection(
 | 
				
			||||||
	_selectedTextRange = selection;
 | 
						_selectedTextRange = selection;
 | 
				
			||||||
	_selectedText = (selection.from != selection.to)
 | 
						_selectedText = (selection.from != selection.to)
 | 
				
			||||||
		? view->selectedText(selection)
 | 
							? view->selectedText(selection)
 | 
				
			||||||
		: TextWithEntities();
 | 
							: TextForMimeData();
 | 
				
			||||||
	repaintItem(view);
 | 
						repaintItem(view);
 | 
				
			||||||
	if (!_wasSelectedText && !_selectedText.text.isEmpty()) {
 | 
						if (!_wasSelectedText && !_selectedText.empty()) {
 | 
				
			||||||
		_wasSelectedText = true;
 | 
							_wasSelectedText = true;
 | 
				
			||||||
		setFocus();
 | 
							setFocus();
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					@ -1394,7 +1394,7 @@ void ListWidget::applyDragSelection(SelectedMap &applyTo) const {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
TextWithEntities ListWidget::getSelectedText() const {
 | 
					TextForMimeData ListWidget::getSelectedText() const {
 | 
				
			||||||
	auto selected = _selected;
 | 
						auto selected = _selected;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (_mouseAction == MouseAction::Selecting && !_dragSelected.empty()) {
 | 
						if (_mouseAction == MouseAction::Selecting && !_dragSelected.empty()) {
 | 
				
			||||||
| 
						 | 
					@ -1413,20 +1413,20 @@ TextWithEntities ListWidget::getSelectedText() const {
 | 
				
			||||||
	auto fullSize = 0;
 | 
						auto fullSize = 0;
 | 
				
			||||||
	auto texts = std::vector<std::pair<
 | 
						auto texts = std::vector<std::pair<
 | 
				
			||||||
		not_null<HistoryItem*>,
 | 
							not_null<HistoryItem*>,
 | 
				
			||||||
		TextWithEntities>>();
 | 
							TextForMimeData>>();
 | 
				
			||||||
	texts.reserve(selected.size());
 | 
						texts.reserve(selected.size());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const auto wrapItem = [&](
 | 
						const auto wrapItem = [&](
 | 
				
			||||||
			not_null<HistoryItem*> item,
 | 
								not_null<HistoryItem*> item,
 | 
				
			||||||
			TextWithEntities &&unwrapped) {
 | 
								TextForMimeData &&unwrapped) {
 | 
				
			||||||
		auto time = ItemDateTime(item).toString(timeFormat);
 | 
							auto time = ItemDateTime(item).toString(timeFormat);
 | 
				
			||||||
		auto part = TextWithEntities();
 | 
							auto part = TextForMimeData();
 | 
				
			||||||
		auto size = item->author()->name.size()
 | 
							auto size = item->author()->name.size()
 | 
				
			||||||
			+ time.size()
 | 
								+ time.size()
 | 
				
			||||||
			+ unwrapped.text.size();
 | 
								+ unwrapped.expanded.size();
 | 
				
			||||||
		part.text.reserve(size);
 | 
							part.reserve(size);
 | 
				
			||||||
		part.text.append(item->author()->name).append(time);
 | 
							part.append(item->author()->name).append(time);
 | 
				
			||||||
		TextUtilities::Append(part, std::move(unwrapped));
 | 
							part.append(std::move(unwrapped));
 | 
				
			||||||
		texts.emplace_back(std::move(item), std::move(part));
 | 
							texts.emplace_back(std::move(item), std::move(part));
 | 
				
			||||||
		fullSize += size;
 | 
							fullSize += size;
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
| 
						 | 
					@ -1457,18 +1457,18 @@ TextWithEntities ListWidget::getSelectedText() const {
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	ranges::sort(texts, [&](
 | 
						ranges::sort(texts, [&](
 | 
				
			||||||
			const std::pair<not_null<HistoryItem*>, TextWithEntities> &a,
 | 
								const std::pair<not_null<HistoryItem*>, TextForMimeData> &a,
 | 
				
			||||||
			const std::pair<not_null<HistoryItem*>, TextWithEntities> &b) {
 | 
								const std::pair<not_null<HistoryItem*>, TextForMimeData> &b) {
 | 
				
			||||||
		return _delegate->listIsLessInOrder(a.first, b.first);
 | 
							return _delegate->listIsLessInOrder(a.first, b.first);
 | 
				
			||||||
	});
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	auto result = TextWithEntities();
 | 
						auto result = TextForMimeData();
 | 
				
			||||||
	auto sep = qsl("\n\n");
 | 
						auto sep = qstr("\n\n");
 | 
				
			||||||
	result.text.reserve(fullSize + (texts.size() - 1) * sep.size());
 | 
						result.reserve(fullSize + (texts.size() - 1) * sep.size());
 | 
				
			||||||
	for (auto i = begin(texts), e = end(texts); i != e;) {
 | 
						for (auto i = begin(texts), e = end(texts); i != e;) {
 | 
				
			||||||
		TextUtilities::Append(result, std::move(i->second));
 | 
							result.append(std::move(i->second));
 | 
				
			||||||
		if (++i != e) {
 | 
							if (++i != e) {
 | 
				
			||||||
			result.text.append(sep);
 | 
								result.append(sep);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return result;
 | 
						return result;
 | 
				
			||||||
| 
						 | 
					@ -1523,11 +1523,11 @@ void ListWidget::keyPressEvent(QKeyEvent *e) {
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	} else if (e == QKeySequence::Copy
 | 
						} else if (e == QKeySequence::Copy
 | 
				
			||||||
		&& (hasSelectedText() || hasSelectedItems())) {
 | 
							&& (hasSelectedText() || hasSelectedItems())) {
 | 
				
			||||||
		SetClipboardWithEntities(getSelectedText());
 | 
							SetClipboardText(getSelectedText());
 | 
				
			||||||
#ifdef Q_OS_MAC
 | 
					#ifdef Q_OS_MAC
 | 
				
			||||||
	} else if (e->key() == Qt::Key_E
 | 
						} else if (e->key() == Qt::Key_E
 | 
				
			||||||
		&& e->modifiers().testFlag(Qt::ControlModifier)) {
 | 
							&& e->modifiers().testFlag(Qt::ControlModifier)) {
 | 
				
			||||||
		SetClipboardWithEntities(getSelectedText(), QClipboard::FindBuffer);
 | 
							SetClipboardText(getSelectedText(), QClipboard::FindBuffer);
 | 
				
			||||||
#endif // Q_OS_MAC
 | 
					#endif // Q_OS_MAC
 | 
				
			||||||
	} else if (e == QKeySequence::Delete) {
 | 
						} else if (e == QKeySequence::Delete) {
 | 
				
			||||||
		_delegate->listDeleteRequest();
 | 
							_delegate->listDeleteRequest();
 | 
				
			||||||
| 
						 | 
					@ -2268,22 +2268,19 @@ std::unique_ptr<QMimeData> ListWidget::prepareDrag() {
 | 
				
			||||||
		_pressItemExact ? _pressItemExact : pressedItem,
 | 
							_pressItemExact ? _pressItemExact : pressedItem,
 | 
				
			||||||
		_pressState);
 | 
							_pressState);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	QList<QUrl> urls;
 | 
						auto urls = QList<QUrl>();
 | 
				
			||||||
	auto text = [&] {
 | 
						const auto selectedText = [&] {
 | 
				
			||||||
		if (uponSelected) {
 | 
							if (uponSelected) {
 | 
				
			||||||
			return getSelectedText();
 | 
								return getSelectedText();
 | 
				
			||||||
		} else if (pressedHandler) {
 | 
							} else if (pressedHandler) {
 | 
				
			||||||
			return TextWithEntities{
 | 
								//if (!sel.isEmpty() && sel.at(0) != '/' && sel.at(0) != '@' && sel.at(0) != '#') {
 | 
				
			||||||
				pressedHandler->dragText(),
 | 
								//	urls.push_back(QUrl::fromEncoded(sel.toUtf8())); // Google Chrome crashes in Mac OS X O_o
 | 
				
			||||||
				EntitiesInText()
 | 
								//}
 | 
				
			||||||
			};
 | 
								return TextForMimeData::Simple(pressedHandler->dragText());
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		return TextWithEntities();
 | 
							return TextForMimeData();
 | 
				
			||||||
		//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(text)) {
 | 
						if (auto mimeData = MimeDataFromText(selectedText)) {
 | 
				
			||||||
		clearDragSelection();
 | 
							clearDragSelection();
 | 
				
			||||||
//		_widget->noSelectingScroll(); #TODO scroll
 | 
					//		_widget->noSelectingScroll(); #TODO scroll
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -153,7 +153,7 @@ public:
 | 
				
			||||||
	bool isBelowPosition(Data::MessagePosition position) const;
 | 
						bool isBelowPosition(Data::MessagePosition position) const;
 | 
				
			||||||
	void highlightMessage(FullMsgId itemId);
 | 
						void highlightMessage(FullMsgId itemId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	TextWithEntities getSelectedText() const;
 | 
						TextForMimeData getSelectedText() const;
 | 
				
			||||||
	MessageIdsList getSelectedItems() const;
 | 
						MessageIdsList getSelectedItems() const;
 | 
				
			||||||
	void cancelSelection();
 | 
						void cancelSelection();
 | 
				
			||||||
	void selectItem(not_null<HistoryItem*> item);
 | 
						void selectItem(not_null<HistoryItem*> item);
 | 
				
			||||||
| 
						 | 
					@ -466,7 +466,7 @@ private:
 | 
				
			||||||
	bool _selectEnabled = false;
 | 
						bool _selectEnabled = false;
 | 
				
			||||||
	HistoryItem *_selectedTextItem = nullptr;
 | 
						HistoryItem *_selectedTextItem = nullptr;
 | 
				
			||||||
	TextSelection _selectedTextRange;
 | 
						TextSelection _selectedTextRange;
 | 
				
			||||||
	TextWithEntities _selectedText;
 | 
						TextForMimeData _selectedText;
 | 
				
			||||||
	SelectedMap _selected;
 | 
						SelectedMap _selected;
 | 
				
			||||||
	base::flat_set<FullMsgId> _dragSelected;
 | 
						base::flat_set<FullMsgId> _dragSelected;
 | 
				
			||||||
	DragSelectAction _dragSelectAction = DragSelectAction::None;
 | 
						DragSelectAction _dragSelectAction = DragSelectAction::None;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1054,19 +1054,17 @@ void Message::updatePressed(QPoint point) {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
TextWithEntities Message::selectedText(TextSelection selection) const {
 | 
					TextForMimeData Message::selectedText(TextSelection selection) const {
 | 
				
			||||||
	const auto item = message();
 | 
						const auto item = message();
 | 
				
			||||||
	const auto media = this->media();
 | 
						const auto media = this->media();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	TextWithEntities logEntryOriginalResult;
 | 
						auto logEntryOriginalResult = TextForMimeData();
 | 
				
			||||||
	auto textResult = item->_text.toTextWithEntities(
 | 
						auto textResult = item->_text.toTextForMimeData(selection);
 | 
				
			||||||
		selection,
 | 
					 | 
				
			||||||
		ExpandLinksAll);
 | 
					 | 
				
			||||||
	auto skipped = skipTextSelection(selection);
 | 
						auto skipped = skipTextSelection(selection);
 | 
				
			||||||
	auto mediaDisplayed = (media && media->isDisplayed());
 | 
						auto mediaDisplayed = (media && media->isDisplayed());
 | 
				
			||||||
	auto mediaResult = (mediaDisplayed || isHiddenByGroup())
 | 
						auto mediaResult = (mediaDisplayed || isHiddenByGroup())
 | 
				
			||||||
		? media->selectedText(skipped)
 | 
							? media->selectedText(skipped)
 | 
				
			||||||
		: TextWithEntities();
 | 
							: TextForMimeData();
 | 
				
			||||||
	if (auto entry = logEntryOriginal()) {
 | 
						if (auto entry = logEntryOriginal()) {
 | 
				
			||||||
		const auto originalSelection = mediaDisplayed
 | 
							const auto originalSelection = mediaDisplayed
 | 
				
			||||||
			? media->skipSelection(skipped)
 | 
								? media->skipSelection(skipped)
 | 
				
			||||||
| 
						 | 
					@ -1074,17 +1072,15 @@ TextWithEntities Message::selectedText(TextSelection selection) const {
 | 
				
			||||||
		logEntryOriginalResult = entry->selectedText(originalSelection);
 | 
							logEntryOriginalResult = entry->selectedText(originalSelection);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	auto result = textResult;
 | 
						auto result = textResult;
 | 
				
			||||||
	if (result.text.isEmpty()) {
 | 
						if (result.empty()) {
 | 
				
			||||||
		result = std::move(mediaResult);
 | 
							result = std::move(mediaResult);
 | 
				
			||||||
	} else if (!mediaResult.text.isEmpty()) {
 | 
						} else if (!mediaResult.empty()) {
 | 
				
			||||||
		result.text += qstr("\n\n");
 | 
							result.append(qstr("\n\n")).append(std::move(mediaResult));
 | 
				
			||||||
		TextUtilities::Append(result, std::move(mediaResult));
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if (result.text.isEmpty()) {
 | 
						if (result.empty()) {
 | 
				
			||||||
		result = std::move(logEntryOriginalResult);
 | 
							result = std::move(logEntryOriginalResult);
 | 
				
			||||||
	} else if (!logEntryOriginalResult.text.isEmpty()) {
 | 
						} else if (!logEntryOriginalResult.empty()) {
 | 
				
			||||||
		result.text += qstr("\n\n");
 | 
							result.append(qstr("\n\n")).append(std::move(logEntryOriginalResult));
 | 
				
			||||||
		TextUtilities::Append(result, std::move(logEntryOriginalResult));
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return result;
 | 
						return result;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -56,7 +56,7 @@ public:
 | 
				
			||||||
		int bottom,
 | 
							int bottom,
 | 
				
			||||||
		QPoint point,
 | 
							QPoint point,
 | 
				
			||||||
		InfoDisplayType type) const override;
 | 
							InfoDisplayType type) const override;
 | 
				
			||||||
	TextWithEntities selectedText(TextSelection selection) const override;
 | 
						TextForMimeData selectedText(TextSelection selection) const override;
 | 
				
			||||||
	TextSelection adjustSelection(
 | 
						TextSelection adjustSelection(
 | 
				
			||||||
		TextSelection selection,
 | 
							TextSelection selection,
 | 
				
			||||||
		TextSelectType type) const override;
 | 
							TextSelectType type) const override;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -530,8 +530,8 @@ TextState Service::textState(QPoint point, StateRequest request) const {
 | 
				
			||||||
void Service::updatePressed(QPoint point) {
 | 
					void Service::updatePressed(QPoint point) {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
TextWithEntities Service::selectedText(TextSelection selection) const {
 | 
					TextForMimeData Service::selectedText(TextSelection selection) const {
 | 
				
			||||||
	return message()->_text.toTextWithEntities(selection);
 | 
						return message()->_text.toTextForMimeData(selection);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
TextSelection Service::adjustSelection(
 | 
					TextSelection Service::adjustSelection(
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -32,7 +32,7 @@ public:
 | 
				
			||||||
		QPoint point,
 | 
							QPoint point,
 | 
				
			||||||
		StateRequest request) const override;
 | 
							StateRequest request) const override;
 | 
				
			||||||
	void updatePressed(QPoint point) override;
 | 
						void updatePressed(QPoint point) override;
 | 
				
			||||||
	TextWithEntities selectedText(TextSelection selection) const override;
 | 
						TextForMimeData selectedText(TextSelection selection) const override;
 | 
				
			||||||
	TextSelection adjustSelection(
 | 
						TextSelection adjustSelection(
 | 
				
			||||||
		TextSelection selection,
 | 
							TextSelection selection,
 | 
				
			||||||
		TextSelectType type) const override;
 | 
							TextSelectType type) const override;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1923,7 +1923,7 @@ void ListWidget::performDrag() {
 | 
				
			||||||
		//	urls.push_back(QUrl::fromEncoded(sel.toUtf8())); // Google Chrome crashes in Mac OS X O_o
 | 
							//	urls.push_back(QUrl::fromEncoded(sel.toUtf8())); // Google Chrome crashes in Mac OS X O_o
 | 
				
			||||||
		//}
 | 
							//}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	//if (auto mimeData = MimeDataFromTextWithEntities(sel)) {
 | 
						//if (auto mimeData = MimeDataFromText(sel)) {
 | 
				
			||||||
	//	clearDragSelection();
 | 
						//	clearDragSelection();
 | 
				
			||||||
	//	_widget->noSelectingScroll();
 | 
						//	_widget->noSelectingScroll();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -276,11 +276,11 @@ object_ptr<Ui::RpWidget> DetailsFiller::setupInfo() {
 | 
				
			||||||
				if (result.text.startsWith(remove)) {
 | 
									if (result.text.startsWith(remove)) {
 | 
				
			||||||
					result.text.remove(0, remove.size());
 | 
										result.text.remove(0, remove.size());
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
				result.entities.push_back(EntityInText(
 | 
									result.entities.push_back({
 | 
				
			||||||
					EntityInTextCustomUrl,
 | 
										EntityType::CustomUrl,
 | 
				
			||||||
					0,
 | 
										0,
 | 
				
			||||||
					result.text.size(),
 | 
										result.text.size(),
 | 
				
			||||||
					link));
 | 
										link });
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			return result;
 | 
								return result;
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -73,21 +73,21 @@ void ConfirmSwitchBox::prepare() {
 | 
				
			||||||
	setTitle(langFactory(lng_language_switch_title));
 | 
						setTitle(langFactory(lng_language_switch_title));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	auto link = TextWithEntities{ lang(lng_language_switch_link) };
 | 
						auto link = TextWithEntities{ lang(lng_language_switch_link) };
 | 
				
			||||||
	link.entities.push_back(EntityInText(
 | 
						link.entities.push_back({
 | 
				
			||||||
		EntityInTextCustomUrl,
 | 
							EntityType::CustomUrl,
 | 
				
			||||||
		0,
 | 
							0,
 | 
				
			||||||
		link.text.size(),
 | 
							link.text.size(),
 | 
				
			||||||
		QString("internal:go_to_translations")));
 | 
							QString("internal:go_to_translations") });
 | 
				
			||||||
	auto name = TextWithEntities{ _name };
 | 
						auto name = TextWithEntities{ _name };
 | 
				
			||||||
	name.entities.push_back(EntityInText(
 | 
						name.entities.push_back({
 | 
				
			||||||
		EntityInTextBold,
 | 
							EntityType::Bold,
 | 
				
			||||||
		0,
 | 
							0,
 | 
				
			||||||
		name.text.size()));
 | 
							name.text.size() });
 | 
				
			||||||
	auto percent = TextWithEntities{ QString::number(_percent) };
 | 
						auto percent = TextWithEntities{ QString::number(_percent) };
 | 
				
			||||||
	percent.entities.push_back(EntityInText(
 | 
						percent.entities.push_back({
 | 
				
			||||||
		EntityInTextBold,
 | 
							EntityType::Bold,
 | 
				
			||||||
		0,
 | 
							0,
 | 
				
			||||||
		percent.text.size()));
 | 
							percent.text.size() });
 | 
				
			||||||
	const auto text = (_official
 | 
						const auto text = (_official
 | 
				
			||||||
		? lng_language_switch_about_official__generic<TextWithEntities>
 | 
							? lng_language_switch_about_official__generic<TextWithEntities>
 | 
				
			||||||
		: lng_language_switch_about_unofficial__generic<TextWithEntities>)(
 | 
							: lng_language_switch_about_unofficial__generic<TextWithEntities>)(
 | 
				
			||||||
| 
						 | 
					@ -134,11 +134,11 @@ void NotReadyBox::prepare() {
 | 
				
			||||||
	setTitle(langFactory(lng_language_not_ready_title));
 | 
						setTitle(langFactory(lng_language_not_ready_title));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	auto link = TextWithEntities{ lang(lng_language_not_ready_link) };
 | 
						auto link = TextWithEntities{ lang(lng_language_not_ready_link) };
 | 
				
			||||||
	link.entities.push_back(EntityInText(
 | 
						link.entities.push_back({
 | 
				
			||||||
		EntityInTextCustomUrl,
 | 
							EntityType::CustomUrl,
 | 
				
			||||||
		0,
 | 
							0,
 | 
				
			||||||
		link.text.size(),
 | 
							link.text.size(),
 | 
				
			||||||
		QString("internal:go_to_translations")));
 | 
							QString("internal:go_to_translations") });
 | 
				
			||||||
	auto name = TextWithEntities{ _name };
 | 
						auto name = TextWithEntities{ _name };
 | 
				
			||||||
	const auto text = lng_language_not_ready_about__generic(
 | 
						const auto text = lng_language_not_ready_about__generic(
 | 
				
			||||||
		lt_lang_name,
 | 
							lt_lang_name,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -530,7 +530,7 @@ void Widget::handleSongChange() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			textWithEntities.text = name + ' ' + date();
 | 
								textWithEntities.text = name + ' ' + date();
 | 
				
			||||||
			textWithEntities.entities.append(EntityInText(
 | 
								textWithEntities.entities.append(EntityInText(
 | 
				
			||||||
				EntityInTextBold,
 | 
									EntityType::Bold,
 | 
				
			||||||
				0,
 | 
									0,
 | 
				
			||||||
				name.size(),
 | 
									name.size(),
 | 
				
			||||||
				QString()));
 | 
									QString()));
 | 
				
			||||||
| 
						 | 
					@ -551,7 +551,7 @@ void Widget::handleSongChange() {
 | 
				
			||||||
				: TextUtilities::Clean(song->title);
 | 
									: TextUtilities::Clean(song->title);
 | 
				
			||||||
			auto dash = QString::fromUtf8(" \xe2\x80\x93 ");
 | 
								auto dash = QString::fromUtf8(" \xe2\x80\x93 ");
 | 
				
			||||||
			textWithEntities.text = song->performer + dash + title;
 | 
								textWithEntities.text = song->performer + dash + title;
 | 
				
			||||||
			textWithEntities.entities.append({ EntityInTextBold, 0, song->performer.size(), QString() });
 | 
								textWithEntities.entities.append({ EntityType::Bold, 0, song->performer.size(), QString() });
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	_nameLabel->setMarkedText(textWithEntities);
 | 
						_nameLabel->setMarkedText(textWithEntities);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -51,13 +51,13 @@ TextWithEntities ComposeNameWithEntities(DocumentData *document) {
 | 
				
			||||||
		result.text = document->filename().isEmpty()
 | 
							result.text = document->filename().isEmpty()
 | 
				
			||||||
			? qsl("Unknown File")
 | 
								? qsl("Unknown File")
 | 
				
			||||||
			: document->filename();
 | 
								: document->filename();
 | 
				
			||||||
		result.entities.push_back({ EntityInTextBold, 0, result.text.size() });
 | 
							result.entities.push_back({ EntityType::Bold, 0, result.text.size() });
 | 
				
			||||||
	} else if (song->performer.isEmpty()) {
 | 
						} else if (song->performer.isEmpty()) {
 | 
				
			||||||
		result.text = song->title;
 | 
							result.text = song->title;
 | 
				
			||||||
		result.entities.push_back({ EntityInTextBold, 0, result.text.size() });
 | 
							result.entities.push_back({ EntityType::Bold, 0, result.text.size() });
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		result.text = song->performer + QString::fromUtf8(" \xe2\x80\x93 ") + (song->title.isEmpty() ? qsl("Unknown Track") : song->title);
 | 
							result.text = song->performer + QString::fromUtf8(" \xe2\x80\x93 ") + (song->title.isEmpty() ? qsl("Unknown Track") : song->title);
 | 
				
			||||||
		result.entities.push_back({ EntityInTextBold, 0, song->performer.size() });
 | 
							result.entities.push_back({ EntityType::Bold, 0, song->performer.size() });
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return result;
 | 
						return result;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -1317,7 +1317,7 @@ Link::Link(
 | 
				
			||||||
	int32 from = 0, till = text.size(), lnk = entities.size();
 | 
						int32 from = 0, till = text.size(), lnk = entities.size();
 | 
				
			||||||
	for (const auto &entity : entities) {
 | 
						for (const auto &entity : entities) {
 | 
				
			||||||
		auto type = entity.type();
 | 
							auto type = entity.type();
 | 
				
			||||||
		if (type != EntityInTextUrl && type != EntityInTextCustomUrl && type != EntityInTextEmail) {
 | 
							if (type != EntityType::Url && type != EntityType::CustomUrl && type != EntityType::Email) {
 | 
				
			||||||
			continue;
 | 
								continue;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		const auto customUrl = entity.data();
 | 
							const auto customUrl = entity.data();
 | 
				
			||||||
| 
						 | 
					@ -1332,7 +1332,7 @@ Link::Link(
 | 
				
			||||||
		--lnk;
 | 
							--lnk;
 | 
				
			||||||
		auto &entity = entities.at(lnk);
 | 
							auto &entity = entities.at(lnk);
 | 
				
			||||||
		auto type = entity.type();
 | 
							auto type = entity.type();
 | 
				
			||||||
		if (type != EntityInTextUrl && type != EntityInTextCustomUrl && type != EntityInTextEmail) {
 | 
							if (type != EntityType::Url && type != EntityType::CustomUrl && type != EntityType::Email) {
 | 
				
			||||||
			++lnk;
 | 
								++lnk;
 | 
				
			||||||
			break;
 | 
								break;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -129,11 +129,11 @@ void VerifyBox::setupControls(
 | 
				
			||||||
		small);
 | 
							small);
 | 
				
			||||||
	if (resend) {
 | 
						if (resend) {
 | 
				
			||||||
		auto link = TextWithEntities{ lang(lng_cloud_password_resend) };
 | 
							auto link = TextWithEntities{ lang(lng_cloud_password_resend) };
 | 
				
			||||||
		link.entities.push_back(EntityInText(
 | 
							link.entities.push_back({
 | 
				
			||||||
			EntityInTextCustomUrl,
 | 
								EntityType::CustomUrl,
 | 
				
			||||||
			0,
 | 
								0,
 | 
				
			||||||
			link.text.size(),
 | 
								link.text.size(),
 | 
				
			||||||
			QString("internal:resend")));
 | 
								QString("internal:resend") });
 | 
				
			||||||
		const auto label = _content->add(
 | 
							const auto label = _content->add(
 | 
				
			||||||
			object_ptr<Ui::FlatLabel>(
 | 
								object_ptr<Ui::FlatLabel>(
 | 
				
			||||||
				_content,
 | 
									_content,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -252,11 +252,11 @@ void SetupRows(
 | 
				
			||||||
			return username;
 | 
								return username;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		auto result = TextWithEntities{ add };
 | 
							auto result = TextWithEntities{ add };
 | 
				
			||||||
		result.entities.push_back(EntityInText(
 | 
							result.entities.push_back({
 | 
				
			||||||
			EntityInTextCustomUrl,
 | 
								EntityType::CustomUrl,
 | 
				
			||||||
			0,
 | 
								0,
 | 
				
			||||||
			add.size(),
 | 
								add.size(),
 | 
				
			||||||
			"internal:edit_username"));
 | 
								"internal:edit_username" });
 | 
				
			||||||
		return result;
 | 
							return result;
 | 
				
			||||||
	});
 | 
						});
 | 
				
			||||||
	AddRow(
 | 
						AddRow(
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -253,40 +253,40 @@ public:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		int32 startFlags = 0;
 | 
							int32 startFlags = 0;
 | 
				
			||||||
		QString linkData, linkText;
 | 
							QString linkData, linkText;
 | 
				
			||||||
		auto type = waitingEntity->type(), linkType = EntityInTextInvalid;
 | 
							auto type = waitingEntity->type(), linkType = EntityType::Invalid;
 | 
				
			||||||
		LinkDisplayStatus linkDisplayStatus = LinkDisplayedFull;
 | 
							LinkDisplayStatus linkDisplayStatus = LinkDisplayedFull;
 | 
				
			||||||
		if (type == EntityInTextBold) {
 | 
							if (type == EntityType::Bold) {
 | 
				
			||||||
			startFlags = TextBlockFSemibold;
 | 
								startFlags = TextBlockFSemibold;
 | 
				
			||||||
		} else if (type == EntityInTextItalic) {
 | 
							} else if (type == EntityType::Italic) {
 | 
				
			||||||
			startFlags = TextBlockFItalic;
 | 
								startFlags = TextBlockFItalic;
 | 
				
			||||||
		} else if (type == EntityInTextCode) {
 | 
							} else if (type == EntityType::Code) {
 | 
				
			||||||
			startFlags = TextBlockFCode;
 | 
								startFlags = TextBlockFCode;
 | 
				
			||||||
		} else if (type == EntityInTextPre) {
 | 
							} else if (type == EntityType::Pre) {
 | 
				
			||||||
			startFlags = TextBlockFPre;
 | 
								startFlags = TextBlockFPre;
 | 
				
			||||||
			createBlock();
 | 
								createBlock();
 | 
				
			||||||
			if (!_t->_blocks.empty() && _t->_blocks.back()->type() != TextBlockTNewline) {
 | 
								if (!_t->_blocks.empty() && _t->_blocks.back()->type() != TextBlockTNewline) {
 | 
				
			||||||
				createNewlineBlock();
 | 
									createNewlineBlock();
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		} else if (type == EntityInTextUrl
 | 
							} else if (type == EntityType::Url
 | 
				
			||||||
		        || type == EntityInTextEmail
 | 
							        || type == EntityType::Email
 | 
				
			||||||
		        || type == EntityInTextMention
 | 
							        || type == EntityType::Mention
 | 
				
			||||||
		        || type == EntityInTextHashtag
 | 
							        || type == EntityType::Hashtag
 | 
				
			||||||
		        || type == EntityInTextCashtag
 | 
							        || type == EntityType::Cashtag
 | 
				
			||||||
		        || type == EntityInTextBotCommand) {
 | 
							        || type == EntityType::BotCommand) {
 | 
				
			||||||
			linkType = type;
 | 
								linkType = type;
 | 
				
			||||||
			linkData = QString(start + waitingEntity->offset(), waitingEntity->length());
 | 
								linkData = QString(start + waitingEntity->offset(), waitingEntity->length());
 | 
				
			||||||
			if (linkType == EntityInTextUrl) {
 | 
								if (linkType == EntityType::Url) {
 | 
				
			||||||
				computeLinkText(linkData, &linkText, &linkDisplayStatus);
 | 
									computeLinkText(linkData, &linkText, &linkDisplayStatus);
 | 
				
			||||||
			} else {
 | 
								} else {
 | 
				
			||||||
				linkText = linkData;
 | 
									linkText = linkData;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		} else if (type == EntityInTextCustomUrl || type == EntityInTextMentionName) {
 | 
							} else if (type == EntityType::CustomUrl || type == EntityType::MentionName) {
 | 
				
			||||||
			linkType = type;
 | 
								linkType = type;
 | 
				
			||||||
			linkData = waitingEntity->data();
 | 
								linkData = waitingEntity->data();
 | 
				
			||||||
			linkText = QString(start + waitingEntity->offset(), waitingEntity->length());
 | 
								linkText = QString(start + waitingEntity->offset(), waitingEntity->length());
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (linkType != EntityInTextInvalid) {
 | 
							if (linkType != EntityType::Invalid) {
 | 
				
			||||||
			createBlock();
 | 
								createBlock();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			links.push_back(TextLinkData(linkType, linkText, linkData, linkDisplayStatus));
 | 
								links.push_back(TextLinkData(linkType, linkText, linkData, linkDisplayStatus));
 | 
				
			||||||
| 
						 | 
					@ -419,7 +419,7 @@ public:
 | 
				
			||||||
		case TextCommandLinkText: {
 | 
							case TextCommandLinkText: {
 | 
				
			||||||
			createBlock();
 | 
								createBlock();
 | 
				
			||||||
			int32 len = ptr->unicode();
 | 
								int32 len = ptr->unicode();
 | 
				
			||||||
			links.push_back(TextLinkData(EntityInTextCustomUrl, QString(), QString(++ptr, len), LinkDisplayedFull));
 | 
								links.push_back(TextLinkData(EntityType::CustomUrl, QString(), QString(++ptr, len), LinkDisplayedFull));
 | 
				
			||||||
			lnkIndex = 0x8000 + links.size();
 | 
								lnkIndex = 0x8000 + links.size();
 | 
				
			||||||
		} break;
 | 
							} break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -535,11 +535,11 @@ public:
 | 
				
			||||||
				const QChar s = source.text.size();
 | 
									const QChar s = source.text.size();
 | 
				
			||||||
				for (; i < l; ++i) {
 | 
									for (; i < l; ++i) {
 | 
				
			||||||
					auto type = preparsed.at(i).type();
 | 
										auto type = preparsed.at(i).type();
 | 
				
			||||||
					if (((type == EntityInTextMention || type == EntityInTextMentionName) && !parseMentions) ||
 | 
										if (((type == EntityType::Mention || type == EntityType::MentionName) && !parseMentions) ||
 | 
				
			||||||
						(type == EntityInTextHashtag && !parseHashtags) ||
 | 
											(type == EntityType::Hashtag && !parseHashtags) ||
 | 
				
			||||||
						(type == EntityInTextCashtag && !parseHashtags) ||
 | 
											(type == EntityType::Cashtag && !parseHashtags) ||
 | 
				
			||||||
						(type == EntityInTextBotCommand && !parseBotCommands) ||
 | 
											(type == EntityType::BotCommand && !parseBotCommands) ||
 | 
				
			||||||
						((type == EntityInTextBold || type == EntityInTextItalic || type == EntityInTextCode || type == EntityInTextPre) && !parseMarkdown)) {
 | 
											((type == EntityType::Bold || type == EntityType::Italic || type == EntityType::Code || type == EntityType::Pre) && !parseMarkdown)) {
 | 
				
			||||||
						continue;
 | 
											continue;
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
					source.entities.push_back(preparsed.at(i));
 | 
										source.entities.push_back(preparsed.at(i));
 | 
				
			||||||
| 
						 | 
					@ -557,14 +557,14 @@ public:
 | 
				
			||||||
	bool isLinkEntity(const EntityInText &entity) const {
 | 
						bool isLinkEntity(const EntityInText &entity) const {
 | 
				
			||||||
		const auto type = entity.type();
 | 
							const auto type = entity.type();
 | 
				
			||||||
		const auto urls = {
 | 
							const auto urls = {
 | 
				
			||||||
			EntityInTextUrl,
 | 
								EntityType::Url,
 | 
				
			||||||
			EntityInTextCustomUrl,
 | 
								EntityType::CustomUrl,
 | 
				
			||||||
			EntityInTextEmail,
 | 
								EntityType::Email,
 | 
				
			||||||
			EntityInTextHashtag,
 | 
								EntityType::Hashtag,
 | 
				
			||||||
			EntityInTextCashtag,
 | 
								EntityType::Cashtag,
 | 
				
			||||||
			EntityInTextMention,
 | 
								EntityType::Mention,
 | 
				
			||||||
			EntityInTextMentionName,
 | 
								EntityType::MentionName,
 | 
				
			||||||
			EntityInTextBotCommand
 | 
								EntityType::BotCommand
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
		return ranges::find(urls, type) != std::end(urls);
 | 
							return ranges::find(urls, type) != std::end(urls);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					@ -582,7 +582,9 @@ public:
 | 
				
			||||||
		while (waitingEntity != entitiesEnd && isInvalidEntity(*waitingEntity)) {
 | 
							while (waitingEntity != entitiesEnd && isInvalidEntity(*waitingEntity)) {
 | 
				
			||||||
			++waitingEntity;
 | 
								++waitingEntity;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		int firstMonospaceOffset = EntityInText::firstMonospaceOffset(source.entities, end - start);
 | 
							const auto firstMonospaceOffset = EntityInText::FirstMonospaceOffset(
 | 
				
			||||||
 | 
								source.entities,
 | 
				
			||||||
 | 
								end - start);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		ptr = start;
 | 
							ptr = start;
 | 
				
			||||||
		while (ptr != end && chIsTrimmed(*ptr, rich) && ptr != start + firstMonospaceOffset) {
 | 
							while (ptr != end && chIsTrimmed(*ptr, rich) && ptr != start + firstMonospaceOffset) {
 | 
				
			||||||
| 
						 | 
					@ -599,7 +601,7 @@ public:
 | 
				
			||||||
		sumWidth = 0;
 | 
							sumWidth = 0;
 | 
				
			||||||
		sumFinished = newlineAwaited = false;
 | 
							sumFinished = newlineAwaited = false;
 | 
				
			||||||
		blockStart = 0;
 | 
							blockStart = 0;
 | 
				
			||||||
		emoji = 0;
 | 
							emoji = nullptr;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		ch = emojiLookback = 0;
 | 
							ch = emojiLookback = 0;
 | 
				
			||||||
		lastSkipped = false;
 | 
							lastSkipped = false;
 | 
				
			||||||
| 
						 | 
					@ -625,8 +627,8 @@ public:
 | 
				
			||||||
		removeFlags.clear();
 | 
							removeFlags.clear();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		_t->_links.resize(maxLnkIndex);
 | 
							_t->_links.resize(maxLnkIndex);
 | 
				
			||||||
		for (auto i = _t->_blocks.cbegin(), e = _t->_blocks.cend(); i != e; ++i) {
 | 
							for (const auto &block : _t->_blocks) {
 | 
				
			||||||
			auto b = i->get();
 | 
								const auto b = block.get();
 | 
				
			||||||
			if (b->lnkIndex() > 0x8000) {
 | 
								if (b->lnkIndex() > 0x8000) {
 | 
				
			||||||
				lnkIndex = maxLnkIndex + (b->lnkIndex() - 0x8000);
 | 
									lnkIndex = maxLnkIndex + (b->lnkIndex() - 0x8000);
 | 
				
			||||||
				if (_t->_links.size() < lnkIndex) {
 | 
									if (_t->_links.size() < lnkIndex) {
 | 
				
			||||||
| 
						 | 
					@ -634,15 +636,15 @@ public:
 | 
				
			||||||
					auto &link = links[lnkIndex - maxLnkIndex - 1];
 | 
										auto &link = links[lnkIndex - maxLnkIndex - 1];
 | 
				
			||||||
					auto handler = ClickHandlerPtr();
 | 
										auto handler = ClickHandlerPtr();
 | 
				
			||||||
					switch (link.type) {
 | 
										switch (link.type) {
 | 
				
			||||||
					case EntityInTextCustomUrl: {
 | 
										case EntityType::CustomUrl: {
 | 
				
			||||||
						if (!link.data.isEmpty()) {
 | 
											if (!link.data.isEmpty()) {
 | 
				
			||||||
							handler = std::make_shared<HiddenUrlClickHandler>(link.data);
 | 
												handler = std::make_shared<HiddenUrlClickHandler>(link.data);
 | 
				
			||||||
						}
 | 
											}
 | 
				
			||||||
					} break;
 | 
										} break;
 | 
				
			||||||
					case EntityInTextEmail:
 | 
										case EntityType::Email:
 | 
				
			||||||
					case EntityInTextUrl: handler = std::make_shared<UrlClickHandler>(link.data, link.displayStatus == LinkDisplayedFull); break;
 | 
										case EntityType::Url: handler = std::make_shared<UrlClickHandler>(link.data, link.displayStatus == LinkDisplayedFull); break;
 | 
				
			||||||
					case EntityInTextBotCommand: handler = std::make_shared<BotCommandClickHandler>(link.data); break;
 | 
										case EntityType::BotCommand: handler = std::make_shared<BotCommandClickHandler>(link.data); break;
 | 
				
			||||||
					case EntityInTextHashtag:
 | 
										case EntityType::Hashtag:
 | 
				
			||||||
						if (options.flags & TextTwitterMentions) {
 | 
											if (options.flags & TextTwitterMentions) {
 | 
				
			||||||
							handler = std::make_shared<UrlClickHandler>(qsl("https://twitter.com/hashtag/") + link.data.mid(1) + qsl("?src=hash"), true);
 | 
												handler = std::make_shared<UrlClickHandler>(qsl("https://twitter.com/hashtag/") + link.data.mid(1) + qsl("?src=hash"), true);
 | 
				
			||||||
						} else if (options.flags & TextInstagramMentions) {
 | 
											} else if (options.flags & TextInstagramMentions) {
 | 
				
			||||||
| 
						 | 
					@ -651,10 +653,10 @@ public:
 | 
				
			||||||
							handler = std::make_shared<HashtagClickHandler>(link.data);
 | 
												handler = std::make_shared<HashtagClickHandler>(link.data);
 | 
				
			||||||
						}
 | 
											}
 | 
				
			||||||
					break;
 | 
										break;
 | 
				
			||||||
					case EntityInTextCashtag:
 | 
										case EntityType::Cashtag:
 | 
				
			||||||
						handler = std::make_shared<CashtagClickHandler>(link.data);
 | 
											handler = std::make_shared<CashtagClickHandler>(link.data);
 | 
				
			||||||
						break;
 | 
											break;
 | 
				
			||||||
					case EntityInTextMention:
 | 
										case EntityType::Mention:
 | 
				
			||||||
						if (options.flags & TextTwitterMentions) {
 | 
											if (options.flags & TextTwitterMentions) {
 | 
				
			||||||
							handler = std::make_shared<UrlClickHandler>(qsl("https://twitter.com/") + link.data.mid(1), true);
 | 
												handler = std::make_shared<UrlClickHandler>(qsl("https://twitter.com/") + link.data.mid(1), true);
 | 
				
			||||||
						} else if (options.flags & TextInstagramMentions) {
 | 
											} else if (options.flags & TextInstagramMentions) {
 | 
				
			||||||
| 
						 | 
					@ -663,7 +665,7 @@ public:
 | 
				
			||||||
							handler = std::make_shared<MentionClickHandler>(link.data);
 | 
												handler = std::make_shared<MentionClickHandler>(link.data);
 | 
				
			||||||
						}
 | 
											}
 | 
				
			||||||
					break;
 | 
										break;
 | 
				
			||||||
					case EntityInTextMentionName: {
 | 
										case EntityType::MentionName: {
 | 
				
			||||||
						auto fields = TextUtilities::MentionNameDataToFields(link.data);
 | 
											auto fields = TextUtilities::MentionNameDataToFields(link.data);
 | 
				
			||||||
						if (fields.userId) {
 | 
											if (fields.userId) {
 | 
				
			||||||
							handler = std::make_shared<MentionNameClickHandler>(link.text, fields.userId, fields.accessHash);
 | 
												handler = std::make_shared<MentionNameClickHandler>(link.text, fields.userId, fields.accessHash);
 | 
				
			||||||
| 
						 | 
					@ -693,13 +695,13 @@ private:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	struct TextLinkData {
 | 
						struct TextLinkData {
 | 
				
			||||||
		TextLinkData() = default;
 | 
							TextLinkData() = default;
 | 
				
			||||||
		TextLinkData(EntityInTextType type, const QString &text, const QString &data, LinkDisplayStatus displayStatus)
 | 
							TextLinkData(EntityType type, const QString &text, const QString &data, LinkDisplayStatus displayStatus)
 | 
				
			||||||
			: type(type)
 | 
								: type(type)
 | 
				
			||||||
			, text(text)
 | 
								, text(text)
 | 
				
			||||||
			, data(data)
 | 
								, data(data)
 | 
				
			||||||
			, displayStatus(displayStatus) {
 | 
								, displayStatus(displayStatus) {
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		EntityInTextType type = EntityInTextInvalid;
 | 
							EntityType type = EntityType::Invalid;
 | 
				
			||||||
		QString text, data;
 | 
							QString text, data;
 | 
				
			||||||
		LinkDisplayStatus displayStatus = LinkDisplayedFull;
 | 
							LinkDisplayStatus displayStatus = LinkDisplayedFull;
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
| 
						 | 
					@ -2927,13 +2929,11 @@ void Text::drawElided(Painter &painter, int32 left, int32 top, int32 w, int32 li
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Text::StateResult Text::getState(QPoint point, int width, StateRequest request) const {
 | 
					Text::StateResult Text::getState(QPoint point, int width, StateRequest request) const {
 | 
				
			||||||
	TextPainter p(0, this);
 | 
						return TextPainter(nullptr, this).getState(point, width, request);
 | 
				
			||||||
	return p.getState(point, width, request);
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Text::StateResult Text::getStateElided(QPoint point, int width, StateRequestElided request) const {
 | 
					Text::StateResult Text::getStateElided(QPoint point, int width, StateRequestElided request) const {
 | 
				
			||||||
	TextPainter p(0, this);
 | 
						return TextPainter(nullptr, this).getStateElided(point, width, request);
 | 
				
			||||||
	return p.getStateElided(point, width, request);
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
TextSelection Text::adjustSelection(TextSelection selection, TextSelectType selectType) const {
 | 
					TextSelection Text::adjustSelection(TextSelection selection, TextSelectType selectType) const {
 | 
				
			||||||
| 
						 | 
					@ -3046,65 +3046,102 @@ void Text::enumerateText(TextSelection selection, AppendPartCallback appendPartC
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					QString Text::toString(TextSelection selection) const {
 | 
				
			||||||
 | 
						return toText(selection, false, false).rich.text;
 | 
				
			||||||
TextWithEntities Text::toTextWithEntities(TextSelection selection, ExpandLinksMode mode) const {
 | 
					 | 
				
			||||||
	TextWithEntities result;
 | 
					 | 
				
			||||||
	result.text.reserve(_text.size());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	int lnkStart = 0, italicStart = 0, boldStart = 0, codeStart = 0, preStart = 0;
 | 
					 | 
				
			||||||
	auto flagsChangeCallback = [&](int32 oldFlags, int32 newFlags) {
 | 
					 | 
				
			||||||
		if ((oldFlags & TextBlockFItalic) && !(newFlags & TextBlockFItalic)) { // write italic
 | 
					 | 
				
			||||||
			result.entities.push_back(EntityInText(EntityInTextItalic, italicStart, result.text.size() - italicStart));
 | 
					 | 
				
			||||||
		} else if ((newFlags & TextBlockFItalic) && !(oldFlags & TextBlockFItalic)) {
 | 
					 | 
				
			||||||
			italicStart = result.text.size();
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		if ((oldFlags & TextBlockFSemibold) && !(newFlags & TextBlockFSemibold)) {
 | 
					 | 
				
			||||||
			result.entities.push_back(EntityInText(EntityInTextBold, boldStart, result.text.size() - boldStart));
 | 
					 | 
				
			||||||
		} else if ((newFlags & TextBlockFSemibold) && !(oldFlags & TextBlockFSemibold)) {
 | 
					 | 
				
			||||||
			boldStart = result.text.size();
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		if ((oldFlags & TextBlockFCode) && !(newFlags & TextBlockFCode)) {
 | 
					 | 
				
			||||||
			result.entities.push_back(EntityInText(EntityInTextCode, codeStart, result.text.size() - codeStart));
 | 
					 | 
				
			||||||
		} else if ((newFlags & TextBlockFCode) && !(oldFlags & TextBlockFCode)) {
 | 
					 | 
				
			||||||
			codeStart = result.text.size();
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		if ((oldFlags & TextBlockFPre) && !(newFlags & TextBlockFPre)) {
 | 
					 | 
				
			||||||
			result.entities.push_back(EntityInText(EntityInTextPre, preStart, result.text.size() - preStart));
 | 
					 | 
				
			||||||
		} else if ((newFlags & TextBlockFPre) && !(oldFlags & TextBlockFPre)) {
 | 
					 | 
				
			||||||
			preStart = result.text.size();
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	};
 | 
					 | 
				
			||||||
	auto clickHandlerStartCallback = [&] {
 | 
					 | 
				
			||||||
		lnkStart = result.text.size();
 | 
					 | 
				
			||||||
	};
 | 
					 | 
				
			||||||
	auto clickHandlerFinishCallback = [&](const QStringRef &r, const ClickHandlerPtr &handler) {
 | 
					 | 
				
			||||||
		const auto expanded = handler->getExpandedLinkTextWithEntities(lnkStart, r);
 | 
					 | 
				
			||||||
		if (mode == ExpandLinksAll
 | 
					 | 
				
			||||||
			&& !expanded.entities.isEmpty()
 | 
					 | 
				
			||||||
			&& expanded.entities[0].type() == EntityInTextCustomUrl) {
 | 
					 | 
				
			||||||
			result.text += r;
 | 
					 | 
				
			||||||
			result.text += qsl(" (") + expanded.entities[0].data() + ')';
 | 
					 | 
				
			||||||
		} else if (expanded.text.isEmpty()) {
 | 
					 | 
				
			||||||
			result.text += r;
 | 
					 | 
				
			||||||
		} else {
 | 
					 | 
				
			||||||
			result.text += expanded.text;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		if (!expanded.entities.isEmpty()) {
 | 
					 | 
				
			||||||
			result.entities.append(expanded.entities);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	};
 | 
					 | 
				
			||||||
	auto appendPartCallback = [&](const QStringRef &r) {
 | 
					 | 
				
			||||||
		result.text += r;
 | 
					 | 
				
			||||||
	};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	enumerateText(selection, appendPartCallback, clickHandlerStartCallback, clickHandlerFinishCallback, flagsChangeCallback);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return result;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
QString Text::toString(TextSelection selection, ExpandLinksMode mode) const {
 | 
					TextWithEntities Text::toTextWithEntities(TextSelection selection) const {
 | 
				
			||||||
	return toTextWithEntities(selection, mode).text;
 | 
						return toText(selection, false, true).rich;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					TextForMimeData Text::toTextForMimeData(TextSelection selection) const {
 | 
				
			||||||
 | 
						return toText(selection, true, true);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					TextForMimeData Text::toText(
 | 
				
			||||||
 | 
							TextSelection selection,
 | 
				
			||||||
 | 
							bool composeExpanded,
 | 
				
			||||||
 | 
							bool composeEntities) const {
 | 
				
			||||||
 | 
						struct MarkdownTagTracker {
 | 
				
			||||||
 | 
							TextBlockFlags flag = TextBlockFlags();
 | 
				
			||||||
 | 
							EntityType type = EntityType();
 | 
				
			||||||
 | 
							int start = 0;
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
						auto result = TextForMimeData();
 | 
				
			||||||
 | 
						result.rich.text.reserve(_text.size());
 | 
				
			||||||
 | 
						if (composeExpanded) {
 | 
				
			||||||
 | 
							result.expanded.reserve(_text.size());
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						auto linkStart = 0;
 | 
				
			||||||
 | 
						auto markdownTrackers = composeEntities
 | 
				
			||||||
 | 
							? std::vector<MarkdownTagTracker>{
 | 
				
			||||||
 | 
								{ TextBlockFItalic, EntityType::Italic },
 | 
				
			||||||
 | 
								{ TextBlockFSemibold, EntityType::Bold },
 | 
				
			||||||
 | 
								{ TextBlockFCode, EntityType::Code },
 | 
				
			||||||
 | 
								{ TextBlockFPre, EntityType::Pre }
 | 
				
			||||||
 | 
							} : std::vector<MarkdownTagTracker>();
 | 
				
			||||||
 | 
						const auto flagsChangeCallback = [&](int32 oldFlags, int32 newFlags) {
 | 
				
			||||||
 | 
							if (!composeEntities) {
 | 
				
			||||||
 | 
								return;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							for (auto &tracker : markdownTrackers) {
 | 
				
			||||||
 | 
								const auto flag = tracker.flag;
 | 
				
			||||||
 | 
								if ((oldFlags & flag) && !(newFlags & flag)) {
 | 
				
			||||||
 | 
									result.rich.entities.push_back({
 | 
				
			||||||
 | 
										tracker.type,
 | 
				
			||||||
 | 
										tracker.start,
 | 
				
			||||||
 | 
										result.rich.text.size() - tracker.start });
 | 
				
			||||||
 | 
								} else if ((newFlags & flag) && !(oldFlags & flag)) {
 | 
				
			||||||
 | 
									tracker.start = result.rich.text.size();
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
						const auto clickHandlerStartCallback = [&] {
 | 
				
			||||||
 | 
							linkStart = result.rich.text.size();
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
						const auto clickHandlerFinishCallback = [&](
 | 
				
			||||||
 | 
								const QStringRef &part,
 | 
				
			||||||
 | 
								const ClickHandlerPtr &handler) {
 | 
				
			||||||
 | 
							const auto entity = handler->getTextEntity();
 | 
				
			||||||
 | 
							const auto plainUrl = (entity.type == EntityType::Url)
 | 
				
			||||||
 | 
								|| (entity.type == EntityType::Email);
 | 
				
			||||||
 | 
							const auto full = plainUrl
 | 
				
			||||||
 | 
								? entity.data.midRef(0, entity.data.size())
 | 
				
			||||||
 | 
								: part;
 | 
				
			||||||
 | 
							result.rich.text.append(full);
 | 
				
			||||||
 | 
							if (!composeExpanded && !composeEntities) {
 | 
				
			||||||
 | 
								return;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if (composeExpanded) {
 | 
				
			||||||
 | 
								result.expanded.append(full);
 | 
				
			||||||
 | 
								if (entity.type == EntityType::CustomUrl) {
 | 
				
			||||||
 | 
									const auto &url = entity.data;
 | 
				
			||||||
 | 
									result.expanded.append(qstr(" (")).append(url).append(')');
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if (composeEntities) {
 | 
				
			||||||
 | 
								result.rich.entities.push_back({
 | 
				
			||||||
 | 
									entity.type,
 | 
				
			||||||
 | 
									linkStart,
 | 
				
			||||||
 | 
									full.size(),
 | 
				
			||||||
 | 
									plainUrl ? QString() : entity.data });
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
						const auto appendPartCallback = [&](const QStringRef &part) {
 | 
				
			||||||
 | 
							result.rich.text += part;
 | 
				
			||||||
 | 
							if (composeExpanded) {
 | 
				
			||||||
 | 
								result.expanded += part;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						enumerateText(
 | 
				
			||||||
 | 
							selection,
 | 
				
			||||||
 | 
							appendPartCallback,
 | 
				
			||||||
 | 
							clickHandlerStartCallback,
 | 
				
			||||||
 | 
							clickHandlerFinishCallback,
 | 
				
			||||||
 | 
							flagsChangeCallback);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return result;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void Text::clear() {
 | 
					void Text::clear() {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -165,8 +165,11 @@ public:
 | 
				
			||||||
		return _text.size();
 | 
							return _text.size();
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	QString toString(TextSelection selection = AllTextSelection, ExpandLinksMode mode = ExpandLinksShortened) const;
 | 
						QString toString(TextSelection selection = AllTextSelection) const;
 | 
				
			||||||
	TextWithEntities toTextWithEntities(TextSelection selection = AllTextSelection, ExpandLinksMode mode = ExpandLinksShortened) const;
 | 
						TextWithEntities toTextWithEntities(
 | 
				
			||||||
 | 
							TextSelection selection = AllTextSelection) const;
 | 
				
			||||||
 | 
						TextForMimeData toTextForMimeData(
 | 
				
			||||||
 | 
							TextSelection selection = AllTextSelection) const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	bool lastDots(int32 dots, int32 maxdots = 3) { // hack for typing animation
 | 
						bool lastDots(int32 dots, int32 maxdots = 3) { // hack for typing animation
 | 
				
			||||||
		if (_text.size() < maxdots) return false;
 | 
							if (_text.size() < maxdots) return false;
 | 
				
			||||||
| 
						 | 
					@ -217,6 +220,11 @@ private:
 | 
				
			||||||
	// it is also called from move constructor / assignment operator
 | 
						// it is also called from move constructor / assignment operator
 | 
				
			||||||
	void clearFields();
 | 
						void clearFields();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						TextForMimeData toText(
 | 
				
			||||||
 | 
							TextSelection selection,
 | 
				
			||||||
 | 
							bool composeExpanded,
 | 
				
			||||||
 | 
							bool composeEntities) const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	QFixed _minResizeWidth;
 | 
						QFixed _minResizeWidth;
 | 
				
			||||||
	QFixed _maxWidth = 0;
 | 
						QFixed _maxWidth = 0;
 | 
				
			||||||
	int32 _minHeight = 0;
 | 
						int32 _minHeight = 0;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1321,8 +1321,8 @@ bool CutPart(TextWithEntities &sending, TextWithEntities &left, int32 limit) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (s > half) {
 | 
							if (s > half) {
 | 
				
			||||||
			bool inEntity = (currentEntity < entityCount) && (ch > start + left.entities[currentEntity].offset()) && (ch < start + left.entities[currentEntity].offset() + left.entities[currentEntity].length());
 | 
								bool inEntity = (currentEntity < entityCount) && (ch > start + left.entities[currentEntity].offset()) && (ch < start + left.entities[currentEntity].offset() + left.entities[currentEntity].length());
 | 
				
			||||||
			EntityInTextType entityType = (currentEntity < entityCount) ? left.entities[currentEntity].type() : EntityInTextInvalid;
 | 
								EntityType entityType = (currentEntity < entityCount) ? left.entities[currentEntity].type() : EntityType::Invalid;
 | 
				
			||||||
			bool canBreakEntity = (entityType == EntityInTextPre || entityType == EntityInTextCode);
 | 
								bool canBreakEntity = (entityType == EntityType::Pre || entityType == EntityType::Code);
 | 
				
			||||||
			int32 noEntityLevel = inEntity ? 0 : 1;
 | 
								int32 noEntityLevel = inEntity ? 0 : 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			auto markGoodAsLevel = [&](int newLevel) {
 | 
								auto markGoodAsLevel = [&](int newLevel) {
 | 
				
			||||||
| 
						 | 
					@ -1348,9 +1348,9 @@ bool CutPart(TextWithEntities &sending, TextWithEntities &left, int32 limit) {
 | 
				
			||||||
						}
 | 
											}
 | 
				
			||||||
					} else if (ch + 1 < end && chIsNewline(*(ch + 1))) {
 | 
										} else if (ch + 1 < end && chIsNewline(*(ch + 1))) {
 | 
				
			||||||
						markGoodAsLevel(15);
 | 
											markGoodAsLevel(15);
 | 
				
			||||||
					} else if (currentEntity < entityCount && ch + 1 == start + left.entities[currentEntity].offset() && left.entities[currentEntity].type() == EntityInTextPre) {
 | 
										} else if (currentEntity < entityCount && ch + 1 == start + left.entities[currentEntity].offset() && left.entities[currentEntity].type() == EntityType::Pre) {
 | 
				
			||||||
						markGoodAsLevel(14);
 | 
											markGoodAsLevel(14);
 | 
				
			||||||
					} else if (currentEntity > 0 && ch == start + left.entities[currentEntity - 1].offset() + left.entities[currentEntity - 1].length() && left.entities[currentEntity - 1].type() == EntityInTextPre) {
 | 
										} else if (currentEntity > 0 && ch == start + left.entities[currentEntity - 1].offset() + left.entities[currentEntity - 1].length() && left.entities[currentEntity - 1].type() == EntityType::Pre) {
 | 
				
			||||||
						markGoodAsLevel(14);
 | 
											markGoodAsLevel(14);
 | 
				
			||||||
					} else {
 | 
										} else {
 | 
				
			||||||
						markGoodAsLevel(13);
 | 
											markGoodAsLevel(13);
 | 
				
			||||||
| 
						 | 
					@ -1463,13 +1463,13 @@ EntitiesInText EntitiesFromMTP(const QVector<MTPMessageEntity> &entities) {
 | 
				
			||||||
		result.reserve(entities.size());
 | 
							result.reserve(entities.size());
 | 
				
			||||||
		for_const (auto &entity, entities) {
 | 
							for_const (auto &entity, entities) {
 | 
				
			||||||
			switch (entity.type()) {
 | 
								switch (entity.type()) {
 | 
				
			||||||
			case mtpc_messageEntityUrl: { auto &d = entity.c_messageEntityUrl(); result.push_back(EntityInText(EntityInTextUrl, d.voffset.v, d.vlength.v)); } break;
 | 
								case mtpc_messageEntityUrl: { auto &d = entity.c_messageEntityUrl(); result.push_back({ EntityType::Url, d.voffset.v, d.vlength.v }); } break;
 | 
				
			||||||
			case mtpc_messageEntityTextUrl: { auto &d = entity.c_messageEntityTextUrl(); result.push_back(EntityInText(EntityInTextCustomUrl, d.voffset.v, d.vlength.v, Clean(qs(d.vurl)))); } break;
 | 
								case mtpc_messageEntityTextUrl: { auto &d = entity.c_messageEntityTextUrl(); result.push_back({ EntityType::CustomUrl, d.voffset.v, d.vlength.v, Clean(qs(d.vurl)) }); } break;
 | 
				
			||||||
			case mtpc_messageEntityEmail: { auto &d = entity.c_messageEntityEmail(); result.push_back(EntityInText(EntityInTextEmail, d.voffset.v, d.vlength.v)); } break;
 | 
								case mtpc_messageEntityEmail: { auto &d = entity.c_messageEntityEmail(); result.push_back({ EntityType::Email, d.voffset.v, d.vlength.v }); } break;
 | 
				
			||||||
			case mtpc_messageEntityHashtag: { auto &d = entity.c_messageEntityHashtag(); result.push_back(EntityInText(EntityInTextHashtag, d.voffset.v, d.vlength.v)); } break;
 | 
								case mtpc_messageEntityHashtag: { auto &d = entity.c_messageEntityHashtag(); result.push_back({ EntityType::Hashtag, d.voffset.v, d.vlength.v }); } break;
 | 
				
			||||||
			case mtpc_messageEntityCashtag: { auto &d = entity.c_messageEntityCashtag(); result.push_back(EntityInText(EntityInTextCashtag, d.voffset.v, d.vlength.v)); } break;
 | 
								case mtpc_messageEntityCashtag: { auto &d = entity.c_messageEntityCashtag(); result.push_back({ EntityType::Cashtag, d.voffset.v, d.vlength.v }); } break;
 | 
				
			||||||
			case mtpc_messageEntityPhone: break; // Skipping phones.
 | 
								case mtpc_messageEntityPhone: break; // Skipping phones.
 | 
				
			||||||
			case mtpc_messageEntityMention: { auto &d = entity.c_messageEntityMention(); result.push_back(EntityInText(EntityInTextMention, d.voffset.v, d.vlength.v)); } break;
 | 
								case mtpc_messageEntityMention: { auto &d = entity.c_messageEntityMention(); result.push_back({ EntityType::Mention, d.voffset.v, d.vlength.v }); } break;
 | 
				
			||||||
			case mtpc_messageEntityMentionName: {
 | 
								case mtpc_messageEntityMentionName: {
 | 
				
			||||||
				auto &d = entity.c_messageEntityMentionName();
 | 
									auto &d = entity.c_messageEntityMentionName();
 | 
				
			||||||
				auto data = [&d] {
 | 
									auto data = [&d] {
 | 
				
			||||||
| 
						 | 
					@ -1480,7 +1480,7 @@ EntitiesInText EntitiesFromMTP(const QVector<MTPMessageEntity> &entities) {
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
					return MentionNameDataFromFields(d.vuser_id.v);
 | 
										return MentionNameDataFromFields(d.vuser_id.v);
 | 
				
			||||||
				};
 | 
									};
 | 
				
			||||||
				result.push_back(EntityInText(EntityInTextMentionName, d.voffset.v, d.vlength.v, data()));
 | 
									result.push_back({ EntityType::MentionName, d.voffset.v, d.vlength.v, data() });
 | 
				
			||||||
			} break;
 | 
								} break;
 | 
				
			||||||
			case mtpc_inputMessageEntityMentionName: {
 | 
								case mtpc_inputMessageEntityMentionName: {
 | 
				
			||||||
				auto &d = entity.c_inputMessageEntityMentionName();
 | 
									auto &d = entity.c_inputMessageEntityMentionName();
 | 
				
			||||||
| 
						 | 
					@ -1494,14 +1494,14 @@ EntitiesInText EntitiesFromMTP(const QVector<MTPMessageEntity> &entities) {
 | 
				
			||||||
					return QString();
 | 
										return QString();
 | 
				
			||||||
				})();
 | 
									})();
 | 
				
			||||||
				if (!data.isEmpty()) {
 | 
									if (!data.isEmpty()) {
 | 
				
			||||||
					result.push_back(EntityInText(EntityInTextMentionName, d.voffset.v, d.vlength.v, data));
 | 
										result.push_back({ EntityType::MentionName, d.voffset.v, d.vlength.v, data });
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			} break;
 | 
								} break;
 | 
				
			||||||
			case mtpc_messageEntityBotCommand: { auto &d = entity.c_messageEntityBotCommand(); result.push_back(EntityInText(EntityInTextBotCommand, d.voffset.v, d.vlength.v)); } break;
 | 
								case mtpc_messageEntityBotCommand: { auto &d = entity.c_messageEntityBotCommand(); result.push_back({ EntityType::BotCommand, d.voffset.v, d.vlength.v }); } break;
 | 
				
			||||||
			case mtpc_messageEntityBold: { auto &d = entity.c_messageEntityBold(); result.push_back(EntityInText(EntityInTextBold, d.voffset.v, d.vlength.v)); } break;
 | 
								case mtpc_messageEntityBold: { auto &d = entity.c_messageEntityBold(); result.push_back({ EntityType::Bold, d.voffset.v, d.vlength.v }); } break;
 | 
				
			||||||
			case mtpc_messageEntityItalic: { auto &d = entity.c_messageEntityItalic(); result.push_back(EntityInText(EntityInTextItalic, d.voffset.v, d.vlength.v)); } break;
 | 
								case mtpc_messageEntityItalic: { auto &d = entity.c_messageEntityItalic(); result.push_back({ EntityType::Italic, d.voffset.v, d.vlength.v }); } break;
 | 
				
			||||||
			case mtpc_messageEntityCode: { auto &d = entity.c_messageEntityCode(); result.push_back(EntityInText(EntityInTextCode, d.voffset.v, d.vlength.v)); } break;
 | 
								case mtpc_messageEntityCode: { auto &d = entity.c_messageEntityCode(); result.push_back({ EntityType::Code, d.voffset.v, d.vlength.v }); } break;
 | 
				
			||||||
			case mtpc_messageEntityPre: { auto &d = entity.c_messageEntityPre(); result.push_back(EntityInText(EntityInTextPre, d.voffset.v, d.vlength.v, Clean(qs(d.vlanguage)))); } break;
 | 
								case mtpc_messageEntityPre: { auto &d = entity.c_messageEntityPre(); result.push_back({ EntityType::Pre, d.voffset.v, d.vlength.v, Clean(qs(d.vlanguage)) }); } break;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					@ -1514,25 +1514,25 @@ MTPVector<MTPMessageEntity> EntitiesToMTP(const EntitiesInText &entities, Conver
 | 
				
			||||||
	for_const (auto &entity, entities) {
 | 
						for_const (auto &entity, entities) {
 | 
				
			||||||
		if (entity.length() <= 0) continue;
 | 
							if (entity.length() <= 0) continue;
 | 
				
			||||||
		if (option == ConvertOption::SkipLocal
 | 
							if (option == ConvertOption::SkipLocal
 | 
				
			||||||
			&& entity.type() != EntityInTextBold
 | 
								&& entity.type() != EntityType::Bold
 | 
				
			||||||
			&& entity.type() != EntityInTextItalic
 | 
								&& entity.type() != EntityType::Italic
 | 
				
			||||||
			&& entity.type() != EntityInTextCode
 | 
								&& entity.type() != EntityType::Code
 | 
				
			||||||
			&& entity.type() != EntityInTextPre
 | 
								&& entity.type() != EntityType::Pre
 | 
				
			||||||
			&& entity.type() != EntityInTextMentionName
 | 
								&& entity.type() != EntityType::MentionName
 | 
				
			||||||
			&& entity.type() != EntityInTextCustomUrl) {
 | 
								&& entity.type() != EntityType::CustomUrl) {
 | 
				
			||||||
			continue;
 | 
								continue;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		auto offset = MTP_int(entity.offset());
 | 
							auto offset = MTP_int(entity.offset());
 | 
				
			||||||
		auto length = MTP_int(entity.length());
 | 
							auto length = MTP_int(entity.length());
 | 
				
			||||||
		switch (entity.type()) {
 | 
							switch (entity.type()) {
 | 
				
			||||||
		case EntityInTextUrl: v.push_back(MTP_messageEntityUrl(offset, length)); break;
 | 
							case EntityType::Url: v.push_back(MTP_messageEntityUrl(offset, length)); break;
 | 
				
			||||||
		case EntityInTextCustomUrl: v.push_back(MTP_messageEntityTextUrl(offset, length, MTP_string(entity.data()))); break;
 | 
							case EntityType::CustomUrl: v.push_back(MTP_messageEntityTextUrl(offset, length, MTP_string(entity.data()))); break;
 | 
				
			||||||
		case EntityInTextEmail: v.push_back(MTP_messageEntityEmail(offset, length)); break;
 | 
							case EntityType::Email: v.push_back(MTP_messageEntityEmail(offset, length)); break;
 | 
				
			||||||
		case EntityInTextHashtag: v.push_back(MTP_messageEntityHashtag(offset, length)); break;
 | 
							case EntityType::Hashtag: v.push_back(MTP_messageEntityHashtag(offset, length)); break;
 | 
				
			||||||
		case EntityInTextCashtag: v.push_back(MTP_messageEntityCashtag(offset, length)); break;
 | 
							case EntityType::Cashtag: v.push_back(MTP_messageEntityCashtag(offset, length)); break;
 | 
				
			||||||
		case EntityInTextMention: v.push_back(MTP_messageEntityMention(offset, length)); break;
 | 
							case EntityType::Mention: v.push_back(MTP_messageEntityMention(offset, length)); break;
 | 
				
			||||||
		case EntityInTextMentionName: {
 | 
							case EntityType::MentionName: {
 | 
				
			||||||
			auto inputUser = ([](const QString &data) -> MTPInputUser {
 | 
								auto inputUser = ([](const QString &data) -> MTPInputUser {
 | 
				
			||||||
				auto fields = MentionNameDataToFields(data);
 | 
									auto fields = MentionNameDataToFields(data);
 | 
				
			||||||
				if (fields.userId == Auth().userId()) {
 | 
									if (fields.userId == Auth().userId()) {
 | 
				
			||||||
| 
						 | 
					@ -1546,11 +1546,11 @@ MTPVector<MTPMessageEntity> EntitiesToMTP(const EntitiesInText &entities, Conver
 | 
				
			||||||
				v.push_back(MTP_inputMessageEntityMentionName(offset, length, inputUser));
 | 
									v.push_back(MTP_inputMessageEntityMentionName(offset, length, inputUser));
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		} break;
 | 
							} break;
 | 
				
			||||||
		case EntityInTextBotCommand: v.push_back(MTP_messageEntityBotCommand(offset, length)); break;
 | 
							case EntityType::BotCommand: v.push_back(MTP_messageEntityBotCommand(offset, length)); break;
 | 
				
			||||||
		case EntityInTextBold: v.push_back(MTP_messageEntityBold(offset, length)); break;
 | 
							case EntityType::Bold: v.push_back(MTP_messageEntityBold(offset, length)); break;
 | 
				
			||||||
		case EntityInTextItalic: v.push_back(MTP_messageEntityItalic(offset, length)); break;
 | 
							case EntityType::Italic: v.push_back(MTP_messageEntityItalic(offset, length)); break;
 | 
				
			||||||
		case EntityInTextCode: v.push_back(MTP_messageEntityCode(offset, length)); break;
 | 
							case EntityType::Code: v.push_back(MTP_messageEntityCode(offset, length)); break;
 | 
				
			||||||
		case EntityInTextPre: v.push_back(MTP_messageEntityPre(offset, length, MTP_string(entity.data()))); break;
 | 
							case EntityType::Pre: v.push_back(MTP_messageEntityPre(offset, length, MTP_string(entity.data()))); break;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return MTP_vector<MTPMessageEntity>(std::move(v));
 | 
						return MTP_vector<MTPMessageEntity>(std::move(v));
 | 
				
			||||||
| 
						 | 
					@ -1594,7 +1594,7 @@ void ParseEntities(TextWithEntities &result, int32 flags, bool rich) {
 | 
				
			||||||
		auto mMention = withMentions ? RegExpMention().match(result.text, qMax(mentionSkip, matchOffset)) : QRegularExpressionMatch();
 | 
							auto mMention = withMentions ? RegExpMention().match(result.text, qMax(mentionSkip, matchOffset)) : QRegularExpressionMatch();
 | 
				
			||||||
		auto mBotCommand = withBotCommands ? RegExpBotCommand().match(result.text, matchOffset) : QRegularExpressionMatch();
 | 
							auto mBotCommand = withBotCommands ? RegExpBotCommand().match(result.text, matchOffset) : QRegularExpressionMatch();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		EntityInTextType lnkType = EntityInTextUrl;
 | 
							auto lnkType = EntityType::Url;
 | 
				
			||||||
		int32 lnkStart = 0, lnkLength = 0;
 | 
							int32 lnkStart = 0, lnkLength = 0;
 | 
				
			||||||
		auto domainStart = mDomain.hasMatch() ? mDomain.capturedStart() : kNotFound,
 | 
							auto domainStart = mDomain.hasMatch() ? mDomain.capturedStart() : kNotFound,
 | 
				
			||||||
			domainEnd = mDomain.hasMatch() ? mDomain.capturedEnd() : kNotFound,
 | 
								domainEnd = mDomain.hasMatch() ? mDomain.capturedEnd() : kNotFound,
 | 
				
			||||||
| 
						 | 
					@ -1683,7 +1683,7 @@ void ParseEntities(TextWithEntities &result, int32 flags, bool rich) {
 | 
				
			||||||
				continue;
 | 
									continue;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			lnkType = EntityInTextMention;
 | 
								lnkType = EntityType::Mention;
 | 
				
			||||||
			lnkStart = mentionStart;
 | 
								lnkStart = mentionStart;
 | 
				
			||||||
			lnkLength = mentionEnd - mentionStart;
 | 
								lnkLength = mentionEnd - mentionStart;
 | 
				
			||||||
		} else if (hashtagStart < domainStart
 | 
							} else if (hashtagStart < domainStart
 | 
				
			||||||
| 
						 | 
					@ -1704,7 +1704,7 @@ void ParseEntities(TextWithEntities &result, int32 flags, bool rich) {
 | 
				
			||||||
				continue;
 | 
									continue;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			lnkType = EntityInTextHashtag;
 | 
								lnkType = EntityType::Hashtag;
 | 
				
			||||||
			lnkStart = hashtagStart;
 | 
								lnkStart = hashtagStart;
 | 
				
			||||||
			lnkLength = hashtagEnd - hashtagStart;
 | 
								lnkLength = hashtagEnd - hashtagStart;
 | 
				
			||||||
		} else if (botCommandStart < domainStart) {
 | 
							} else if (botCommandStart < domainStart) {
 | 
				
			||||||
| 
						 | 
					@ -1720,7 +1720,7 @@ void ParseEntities(TextWithEntities &result, int32 flags, bool rich) {
 | 
				
			||||||
				continue;
 | 
									continue;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			lnkType = EntityInTextBotCommand;
 | 
								lnkType = EntityType::BotCommand;
 | 
				
			||||||
			lnkStart = botCommandStart;
 | 
								lnkStart = botCommandStart;
 | 
				
			||||||
			lnkLength = botCommandEnd - botCommandStart;
 | 
								lnkLength = botCommandEnd - botCommandStart;
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
| 
						 | 
					@ -1749,12 +1749,12 @@ void ParseEntities(TextWithEntities &result, int32 flags, bool rich) {
 | 
				
			||||||
					if (mailStart < offset) {
 | 
										if (mailStart < offset) {
 | 
				
			||||||
						mailStart = offset;
 | 
											mailStart = offset;
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
					lnkType = EntityInTextEmail;
 | 
										lnkType = EntityType::Email;
 | 
				
			||||||
					lnkStart = mailStart;
 | 
										lnkStart = mailStart;
 | 
				
			||||||
					lnkLength = domainEnd - mailStart;
 | 
										lnkLength = domainEnd - mailStart;
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			if (lnkType == EntityInTextUrl && !lnkLength) {
 | 
								if (lnkType == EntityType::Url && !lnkLength) {
 | 
				
			||||||
				if (!isProtocolValid || !isTopDomainValid) {
 | 
									if (!isProtocolValid || !isTopDomainValid) {
 | 
				
			||||||
					matchOffset = domainEnd;
 | 
										matchOffset = domainEnd;
 | 
				
			||||||
					continue;
 | 
										continue;
 | 
				
			||||||
| 
						 | 
					@ -1803,7 +1803,7 @@ void ParseEntities(TextWithEntities &result, int32 flags, bool rich) {
 | 
				
			||||||
			newEntities.push_back(entity);
 | 
								newEntities.push_back(entity);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		if (lnkStart >= existingEntityEnd) {
 | 
							if (lnkStart >= existingEntityEnd) {
 | 
				
			||||||
			result.entities.push_back(EntityInText(lnkType, lnkStart, lnkLength));
 | 
								result.entities.push_back({ lnkType, lnkStart, lnkLength });
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		offset = matchOffset = lnkStart + lnkLength;
 | 
							offset = matchOffset = lnkStart + lnkLength;
 | 
				
			||||||
| 
						 | 
					@ -1914,7 +1914,9 @@ void Trim(TextWithEntities &result) {
 | 
				
			||||||
		return;
 | 
							return;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	auto firstMonospaceOffset = EntityInText::firstMonospaceOffset(result.entities, result.text.size());
 | 
						const auto firstMonospaceOffset = EntityInText::FirstMonospaceOffset(
 | 
				
			||||||
 | 
							result.entities,
 | 
				
			||||||
 | 
							result.text.size());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// left trim
 | 
						// left trim
 | 
				
			||||||
	for (auto s = result.text.data(), ch = s, e = s + result.text.size(); ch != e; ++ch) {
 | 
						for (auto s = result.text.data(), ch = s, e = s + result.text.size(); ch != e; ++ch) {
 | 
				
			||||||
| 
						 | 
					@ -1985,8 +1987,39 @@ QString TagsMimeType() {
 | 
				
			||||||
	return qsl("application/x-td-field-tags");
 | 
						return qsl("application/x-td-field-tags");
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					QString TagsTextMimeType() {
 | 
				
			||||||
 | 
						return qsl("application/x-td-field-text");
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
} // namespace TextUtilities
 | 
					} // namespace TextUtilities
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					EntityInText::EntityInText(
 | 
				
			||||||
 | 
						EntityType type,
 | 
				
			||||||
 | 
						int offset,
 | 
				
			||||||
 | 
						int length,
 | 
				
			||||||
 | 
						const QString &data)
 | 
				
			||||||
 | 
					: _type(type)
 | 
				
			||||||
 | 
					, _offset(offset)
 | 
				
			||||||
 | 
					, _length(length)
 | 
				
			||||||
 | 
					, _data(data) {
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					int EntityInText::FirstMonospaceOffset(
 | 
				
			||||||
 | 
							const EntitiesInText &entities,
 | 
				
			||||||
 | 
							int textLength) {
 | 
				
			||||||
 | 
						auto &&monospace = ranges::view::all(
 | 
				
			||||||
 | 
							entities
 | 
				
			||||||
 | 
						) | ranges::view::filter([](const EntityInText & entity) {
 | 
				
			||||||
 | 
							return (entity.type() == EntityType::Pre)
 | 
				
			||||||
 | 
								|| (entity.type() == EntityType::Code);
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
						const auto i = ranges::max_element(
 | 
				
			||||||
 | 
							monospace,
 | 
				
			||||||
 | 
							std::greater<>(),
 | 
				
			||||||
 | 
							&EntityInText::offset);
 | 
				
			||||||
 | 
						return (i == monospace.end()) ? textLength : i->offset();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace Lang {
 | 
					namespace Lang {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
TextWithEntities ReplaceTag<TextWithEntities>::Call(TextWithEntities &&original, ushort tag, const TextWithEntities &replacement) {
 | 
					TextWithEntities ReplaceTag<TextWithEntities>::Call(TextWithEntities &&original, ushort tag, const TextWithEntities &replacement) {
 | 
				
			||||||
| 
						 | 
					@ -2014,7 +2047,7 @@ TextWithEntities ReplaceTag<TextWithEntities>::Call(TextWithEntities &&original,
 | 
				
			||||||
				newOffset = snap(newOffset, replacementPosition, replacementEnd);
 | 
									newOffset = snap(newOffset, replacementPosition, replacementEnd);
 | 
				
			||||||
				newEnd = snap(newEnd, replacementPosition, replacementEnd);
 | 
									newEnd = snap(newEnd, replacementPosition, replacementEnd);
 | 
				
			||||||
				if (auto newLength = newEnd - newOffset) {
 | 
									if (auto newLength = newEnd - newOffset) {
 | 
				
			||||||
					result.entities.push_back(EntityInText(replacementEntity->type(), newOffset, newLength, replacementEntity->data()));
 | 
										result.entities.push_back({ replacementEntity->type(), newOffset, newLength, replacementEntity->data() });
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
				++replacementEntity;
 | 
									++replacementEntity;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
| 
						 | 
					@ -2038,7 +2071,7 @@ TextWithEntities ReplaceTag<TextWithEntities>::Call(TextWithEntities &&original,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// Add a modified original entity.
 | 
								// Add a modified original entity.
 | 
				
			||||||
			if (auto length = end - offset) {
 | 
								if (auto length = end - offset) {
 | 
				
			||||||
				result.entities.push_back(EntityInText(entity.type(), offset, length, entity.data()));
 | 
									result.entities.push_back({ entity.type(), offset, length, entity.data() });
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		// Add the remaining replacement entities.
 | 
							// Add the remaining replacement entities.
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -7,22 +7,22 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 | 
				
			||||||
*/
 | 
					*/
 | 
				
			||||||
#pragma once
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
enum EntityInTextType {
 | 
					enum class EntityType {
 | 
				
			||||||
	EntityInTextInvalid = 0,
 | 
						Invalid = 0,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	EntityInTextUrl,
 | 
						Url,
 | 
				
			||||||
	EntityInTextCustomUrl,
 | 
						CustomUrl,
 | 
				
			||||||
	EntityInTextEmail,
 | 
						Email,
 | 
				
			||||||
	EntityInTextHashtag,
 | 
						Hashtag,
 | 
				
			||||||
	EntityInTextCashtag,
 | 
						Cashtag,
 | 
				
			||||||
	EntityInTextMention,
 | 
						Mention,
 | 
				
			||||||
	EntityInTextMentionName,
 | 
						MentionName,
 | 
				
			||||||
	EntityInTextBotCommand,
 | 
						BotCommand,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	EntityInTextBold,
 | 
						Bold,
 | 
				
			||||||
	EntityInTextItalic,
 | 
						Italic,
 | 
				
			||||||
	EntityInTextCode, // inline
 | 
						Code, // inline
 | 
				
			||||||
	EntityInTextPre,  // block
 | 
						Pre,  // block
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class EntityInText;
 | 
					class EntityInText;
 | 
				
			||||||
| 
						 | 
					@ -30,14 +30,13 @@ using EntitiesInText = QList<EntityInText>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class EntityInText {
 | 
					class EntityInText {
 | 
				
			||||||
public:
 | 
					public:
 | 
				
			||||||
	EntityInText(EntityInTextType type, int offset, int length, const QString &data = QString())
 | 
						EntityInText(
 | 
				
			||||||
		: _type(type)
 | 
							EntityType type,
 | 
				
			||||||
		, _offset(offset)
 | 
							int offset,
 | 
				
			||||||
		, _length(length)
 | 
							int length,
 | 
				
			||||||
		, _data(data) {
 | 
							const QString &data = QString());
 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	EntityInTextType type() const {
 | 
						EntityType type() const {
 | 
				
			||||||
		return _type;
 | 
							return _type;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	int offset() const {
 | 
						int offset() const {
 | 
				
			||||||
| 
						 | 
					@ -79,23 +78,18 @@ public:
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	static int firstMonospaceOffset(const EntitiesInText &entities, int textLength) {
 | 
						static int FirstMonospaceOffset(
 | 
				
			||||||
		int result = textLength;
 | 
							const EntitiesInText &entities,
 | 
				
			||||||
		for_const (auto &entity, entities) {
 | 
							int textLength);
 | 
				
			||||||
			if (entity.type() == EntityInTextPre || entity.type() == EntityInTextCode) {
 | 
					 | 
				
			||||||
				accumulate_min(result, entity.offset());
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		return result;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	explicit operator bool() const {
 | 
						explicit operator bool() const {
 | 
				
			||||||
		return type() != EntityInTextInvalid;
 | 
							return type() != EntityType::Invalid;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
private:
 | 
					private:
 | 
				
			||||||
	EntityInTextType _type;
 | 
						EntityType _type = EntityType::Invalid;
 | 
				
			||||||
	int _offset, _length;
 | 
						int _offset = 0;
 | 
				
			||||||
 | 
						int _length = 0;
 | 
				
			||||||
	QString _data;
 | 
						QString _data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
| 
						 | 
					@ -118,6 +112,39 @@ struct TextWithEntities {
 | 
				
			||||||
	bool empty() const {
 | 
						bool empty() const {
 | 
				
			||||||
		return text.isEmpty();
 | 
							return text.isEmpty();
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						void reserve(int size, int entitiesCount = 0) {
 | 
				
			||||||
 | 
							text.reserve(size);
 | 
				
			||||||
 | 
							entities.reserve(entitiesCount);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						TextWithEntities &append(TextWithEntities &&other) {
 | 
				
			||||||
 | 
							const auto shift = text.size();
 | 
				
			||||||
 | 
							for (auto &entity : other.entities) {
 | 
				
			||||||
 | 
								entity.shiftRight(shift);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							text.append(other.text);
 | 
				
			||||||
 | 
							entities.append(other.entities);
 | 
				
			||||||
 | 
							return *this;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						TextWithEntities &append(const QString &other) {
 | 
				
			||||||
 | 
							text.append(other);
 | 
				
			||||||
 | 
							return *this;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						TextWithEntities &append(QLatin1String other) {
 | 
				
			||||||
 | 
							text.append(other);
 | 
				
			||||||
 | 
							return *this;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						TextWithEntities &append(QChar other) {
 | 
				
			||||||
 | 
							text.append(other);
 | 
				
			||||||
 | 
							return *this;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						static TextWithEntities Simple(const QString &simple) {
 | 
				
			||||||
 | 
							auto result = TextWithEntities();
 | 
				
			||||||
 | 
							result.text = simple;
 | 
				
			||||||
 | 
							return result;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
inline bool operator==(
 | 
					inline bool operator==(
 | 
				
			||||||
| 
						 | 
					@ -132,6 +159,57 @@ inline bool operator!=(
 | 
				
			||||||
	return !(a == b);
 | 
						return !(a == b);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct TextForMimeData {
 | 
				
			||||||
 | 
						QString expanded;
 | 
				
			||||||
 | 
						TextWithEntities rich;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						bool empty() const {
 | 
				
			||||||
 | 
							return expanded.isEmpty();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						void reserve(int size, int entitiesCount = 0) {
 | 
				
			||||||
 | 
							expanded.reserve(size);
 | 
				
			||||||
 | 
							rich.reserve(size, entitiesCount);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						TextForMimeData &append(TextForMimeData &&other) {
 | 
				
			||||||
 | 
							expanded.append(other.expanded);
 | 
				
			||||||
 | 
							rich.append(std::move(other.rich));
 | 
				
			||||||
 | 
							return *this;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						TextForMimeData &append(TextWithEntities &&other) {
 | 
				
			||||||
 | 
							expanded.append(other.text);
 | 
				
			||||||
 | 
							rich.append(std::move(other));
 | 
				
			||||||
 | 
							return *this;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						TextForMimeData &append(const QString &other) {
 | 
				
			||||||
 | 
							expanded.append(other);
 | 
				
			||||||
 | 
							rich.append(other);
 | 
				
			||||||
 | 
							return *this;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						TextForMimeData &append(QLatin1String other) {
 | 
				
			||||||
 | 
							expanded.append(other);
 | 
				
			||||||
 | 
							rich.append(other);
 | 
				
			||||||
 | 
							return *this;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						TextForMimeData &append(QChar other) {
 | 
				
			||||||
 | 
							expanded.append(other);
 | 
				
			||||||
 | 
							rich.append(other);
 | 
				
			||||||
 | 
							return *this;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						static TextForMimeData Rich(TextWithEntities &&rich) {
 | 
				
			||||||
 | 
							auto result = TextForMimeData();
 | 
				
			||||||
 | 
							result.expanded = rich.text;
 | 
				
			||||||
 | 
							result.rich = std::move(rich);
 | 
				
			||||||
 | 
							return result;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						static TextForMimeData Simple(const QString &simple) {
 | 
				
			||||||
 | 
							auto result = TextForMimeData();
 | 
				
			||||||
 | 
							result.expanded = result.rich.text = simple;
 | 
				
			||||||
 | 
							return result;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
enum {
 | 
					enum {
 | 
				
			||||||
	TextParseMultiline = 0x001,
 | 
						TextParseMultiline = 0x001,
 | 
				
			||||||
	TextParseLinks = 0x002,
 | 
						TextParseLinks = 0x002,
 | 
				
			||||||
| 
						 | 
					@ -194,15 +272,6 @@ QString MarkdownCodeBadAfter();
 | 
				
			||||||
QString MarkdownPreGoodBefore();
 | 
					QString MarkdownPreGoodBefore();
 | 
				
			||||||
QString MarkdownPreBadAfter();
 | 
					QString MarkdownPreBadAfter();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
inline void Append(TextWithEntities &to, TextWithEntities &&append) {
 | 
					 | 
				
			||||||
	auto entitiesShiftRight = to.text.size();
 | 
					 | 
				
			||||||
	for (auto &entity : append.entities) {
 | 
					 | 
				
			||||||
		entity.shiftRight(entitiesShiftRight);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	to.text += append.text;
 | 
					 | 
				
			||||||
	to.entities += append.entities;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Text preprocess.
 | 
					// Text preprocess.
 | 
				
			||||||
QString Clean(const QString &text);
 | 
					QString Clean(const QString &text);
 | 
				
			||||||
QString EscapeForRichParsing(const QString &text);
 | 
					QString EscapeForRichParsing(const QString &text);
 | 
				
			||||||
| 
						 | 
					@ -265,6 +334,7 @@ void ApplyServerCleaning(TextWithEntities &result);
 | 
				
			||||||
QByteArray SerializeTags(const TextWithTags::Tags &tags);
 | 
					QByteArray SerializeTags(const TextWithTags::Tags &tags);
 | 
				
			||||||
TextWithTags::Tags DeserializeTags(QByteArray data, int textLength);
 | 
					TextWithTags::Tags DeserializeTags(QByteArray data, int textLength);
 | 
				
			||||||
QString TagsMimeType();
 | 
					QString TagsMimeType();
 | 
				
			||||||
 | 
					QString TagsTextMimeType();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
} // namespace TextUtilities
 | 
					} // namespace TextUtilities
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 | 
				
			||||||
#include "ui/emoji_config.h"
 | 
					#include "ui/emoji_config.h"
 | 
				
			||||||
#include "emoji_suggestions_data.h"
 | 
					#include "emoji_suggestions_data.h"
 | 
				
			||||||
#include "chat_helpers/emoji_suggestions_helper.h"
 | 
					#include "chat_helpers/emoji_suggestions_helper.h"
 | 
				
			||||||
 | 
					#include "chat_helpers/message_field.h" // ConvertTextTagsToEntities
 | 
				
			||||||
#include "window/themes/window_theme.h"
 | 
					#include "window/themes/window_theme.h"
 | 
				
			||||||
#include "lang/lang_keys.h"
 | 
					#include "lang/lang_keys.h"
 | 
				
			||||||
#include "data/data_user.h"
 | 
					#include "data/data_user.h"
 | 
				
			||||||
| 
						 | 
					@ -759,6 +760,33 @@ struct FormattingAction {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					QString ExpandCustomLinks(const TextWithTags &text) {
 | 
				
			||||||
 | 
						const auto entities = ConvertTextTagsToEntities(text.tags);
 | 
				
			||||||
 | 
						auto &&urls = ranges::view::all(
 | 
				
			||||||
 | 
							entities
 | 
				
			||||||
 | 
						) | ranges::view::filter([](const EntityInText &entity) {
 | 
				
			||||||
 | 
							return entity.type() == EntityType::CustomUrl;
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
						const auto &original = text.text;
 | 
				
			||||||
 | 
						if (urls.begin() == urls.end()) {
 | 
				
			||||||
 | 
							return original;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						auto result = QString();
 | 
				
			||||||
 | 
						auto offset = 0;
 | 
				
			||||||
 | 
						for (const auto &entity : urls) {
 | 
				
			||||||
 | 
							const auto till = entity.offset() + entity.length();
 | 
				
			||||||
 | 
							if (till > offset) {
 | 
				
			||||||
 | 
								result.append(original.midRef(offset, till - offset));
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							result.append(qstr(" (")).append(entity.data()).append(')');
 | 
				
			||||||
 | 
							offset = till;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if (original.size() > offset) {
 | 
				
			||||||
 | 
							result.append(original.midRef(offset));
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return result;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
} // namespace
 | 
					} // namespace
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const QString InputField::kTagBold = qsl("**");
 | 
					const QString InputField::kTagBold = qsl("**");
 | 
				
			||||||
| 
						 | 
					@ -2309,13 +2337,16 @@ QMimeData *InputField::createMimeDataFromSelectionInner() const {
 | 
				
			||||||
	const auto end = cursor.selectionEnd();
 | 
						const auto end = cursor.selectionEnd();
 | 
				
			||||||
	if (end > start) {
 | 
						if (end > start) {
 | 
				
			||||||
		auto textWithTags = getTextWithTagsPart(start, end);
 | 
							auto textWithTags = getTextWithTagsPart(start, end);
 | 
				
			||||||
		result->setText(textWithTags.text);
 | 
							result->setText(ExpandCustomLinks(textWithTags));
 | 
				
			||||||
		if (!textWithTags.tags.isEmpty()) {
 | 
							if (!textWithTags.tags.isEmpty()) {
 | 
				
			||||||
			if (_tagMimeProcessor) {
 | 
								if (_tagMimeProcessor) {
 | 
				
			||||||
				for (auto &tag : textWithTags.tags) {
 | 
									for (auto &tag : textWithTags.tags) {
 | 
				
			||||||
					tag.id = _tagMimeProcessor->mimeTagFromTag(tag.id);
 | 
										tag.id = _tagMimeProcessor->mimeTagFromTag(tag.id);
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
								result->setData(
 | 
				
			||||||
 | 
									TextUtilities::TagsTextMimeType(),
 | 
				
			||||||
 | 
									textWithTags.text.toUtf8());
 | 
				
			||||||
			result->setData(
 | 
								result->setData(
 | 
				
			||||||
				TextUtilities::TagsMimeType(),
 | 
									TextUtilities::TagsMimeType(),
 | 
				
			||||||
				TextUtilities::SerializeTags(textWithTags.tags));
 | 
									TextUtilities::SerializeTags(textWithTags.tags));
 | 
				
			||||||
| 
						 | 
					@ -3368,21 +3399,27 @@ void InputField::insertFromMimeDataInner(const QMimeData *source) {
 | 
				
			||||||
		&& _mimeDataHook(source, MimeAction::Insert)) {
 | 
							&& _mimeDataHook(source, MimeAction::Insert)) {
 | 
				
			||||||
		return;
 | 
							return;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	auto mime = TextUtilities::TagsMimeType();
 | 
						const auto text = [&] {
 | 
				
			||||||
	auto text = source->text();
 | 
							const auto textMime = TextUtilities::TagsTextMimeType();
 | 
				
			||||||
	if (source->hasFormat(mime)) {
 | 
							const auto tagsMime = TextUtilities::TagsMimeType();
 | 
				
			||||||
		auto tagsData = source->data(mime);
 | 
							if (!source->hasFormat(textMime) || !source->hasFormat(tagsMime)) {
 | 
				
			||||||
 | 
								_insertedTags.clear();
 | 
				
			||||||
 | 
								return source->text();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							auto result = QString::fromUtf8(source->data(textMime));
 | 
				
			||||||
		_insertedTags = TextUtilities::DeserializeTags(
 | 
							_insertedTags = TextUtilities::DeserializeTags(
 | 
				
			||||||
			tagsData,
 | 
								source->data(tagsMime),
 | 
				
			||||||
			text.size());
 | 
								result.size());
 | 
				
			||||||
		_insertedTagsAreFromMime = true;
 | 
							_insertedTagsAreFromMime = true;
 | 
				
			||||||
	} else {
 | 
							return result;
 | 
				
			||||||
		_insertedTags.clear();
 | 
						}();
 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	auto cursor = textCursor();
 | 
						auto cursor = textCursor();
 | 
				
			||||||
	_realInsertPosition = cursor.selectionStart();
 | 
						_realInsertPosition = cursor.selectionStart();
 | 
				
			||||||
	_realCharsAdded = text.size();
 | 
						_realCharsAdded = text.size();
 | 
				
			||||||
	_inner->QTextEdit::insertFromMimeData(source);
 | 
						if (_realCharsAdded > 0) {
 | 
				
			||||||
 | 
							cursor.insertFragment(QTextDocumentFragment::fromPlainText(text));
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						ensureCursorVisible();
 | 
				
			||||||
	if (!_inDrop) {
 | 
						if (!_inDrop) {
 | 
				
			||||||
		_insertedTags.clear();
 | 
							_insertedTags.clear();
 | 
				
			||||||
		_realInsertPosition = -1;
 | 
							_realInsertPosition = -1;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 | 
				
			||||||
#include "ui/widgets/labels.h"
 | 
					#include "ui/widgets/labels.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "ui/widgets/popup_menu.h"
 | 
					#include "ui/widgets/popup_menu.h"
 | 
				
			||||||
 | 
					#include "chat_helpers/message_field.h" // SetClipboardText/MimeDataFromText
 | 
				
			||||||
#include "mainwindow.h"
 | 
					#include "mainwindow.h"
 | 
				
			||||||
#include "lang/lang_keys.h"
 | 
					#include "lang/lang_keys.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -569,12 +570,12 @@ void FlatLabel::showContextMenu(QContextMenuEvent *e, ContextMenuReason reason)
 | 
				
			||||||
void FlatLabel::onCopySelectedText() {
 | 
					void FlatLabel::onCopySelectedText() {
 | 
				
			||||||
	const auto selection = _selection.empty() ? (_contextMenu ? _savedSelection : _selection) : _selection;
 | 
						const auto selection = _selection.empty() ? (_contextMenu ? _savedSelection : _selection) : _selection;
 | 
				
			||||||
	if (!selection.empty()) {
 | 
						if (!selection.empty()) {
 | 
				
			||||||
		QApplication::clipboard()->setText(_text.toString(selection, ExpandLinksAll));
 | 
							SetClipboardText(_text.toTextForMimeData(selection));
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void FlatLabel::onCopyContextText() {
 | 
					void FlatLabel::onCopyContextText() {
 | 
				
			||||||
	QApplication::clipboard()->setText(_text.toString(AllTextSelection, ExpandLinksAll));
 | 
						SetClipboardText(_text.toTextForMimeData());
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void FlatLabel::onTouchSelect() {
 | 
					void FlatLabel::onTouchSelect() {
 | 
				
			||||||
| 
						 | 
					@ -599,18 +600,18 @@ void FlatLabel::onExecuteDrag() {
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	ClickHandlerPtr pressedHandler = ClickHandler::getPressed();
 | 
						const auto pressedHandler = ClickHandler::getPressed();
 | 
				
			||||||
	QString selectedText;
 | 
						const auto selectedText = [&] {
 | 
				
			||||||
	if (uponSelected) {
 | 
							if (uponSelected) {
 | 
				
			||||||
		selectedText = _text.toString(_selection, ExpandLinksAll);
 | 
								return _text.toTextForMimeData(_selection);
 | 
				
			||||||
	} else if (pressedHandler) {
 | 
							} else if (pressedHandler) {
 | 
				
			||||||
		selectedText = pressedHandler->dragText();
 | 
								return TextForMimeData::Simple(pressedHandler->dragText());
 | 
				
			||||||
	}
 | 
							}
 | 
				
			||||||
	if (!selectedText.isEmpty()) {
 | 
							return TextForMimeData();
 | 
				
			||||||
		auto mimeData = new QMimeData();
 | 
						}();
 | 
				
			||||||
		mimeData->setText(selectedText);
 | 
						if (auto mimeData = MimeDataFromText(selectedText)) {
 | 
				
			||||||
		auto drag = new QDrag(App::wnd());
 | 
							auto drag = new QDrag(App::wnd());
 | 
				
			||||||
		drag->setMimeData(mimeData);
 | 
							drag->setMimeData(mimeData.release());
 | 
				
			||||||
		drag->exec(Qt::CopyAction);
 | 
							drag->exec(Qt::CopyAction);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// We don't receive mouseReleaseEvent when drag is finished.
 | 
							// We don't receive mouseReleaseEvent when drag is finished.
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue