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( | ||||||
|  | 			TextUtilities::ParseEntities( | ||||||
| 				title, | 				title, | ||||||
| 			Ui::WebpageTextTitleOptions().flags); | 				Ui::WebpageTextTitleOptions().flags)); | ||||||
| 		auto descriptionResult = entry->page->description; | 		auto descriptionResult = TextForMimeData::Rich( | ||||||
| 		if (titleResult.text.isEmpty()) { | 			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{ |  | ||||||
| 				pressedHandler->dragText(), |  | ||||||
| 				EntitiesInText() |  | ||||||
| 			}; |  | ||||||
| 		} |  | ||||||
| 		return TextWithEntities(); |  | ||||||
| 			//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()); | ||||||
|  | 		} | ||||||
|  | 		return TextForMimeData(); | ||||||
| 	}(); | 	}(); | ||||||
| 	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 = TextUtilities::DeserializeTags( |  | ||||||
| 			tagsData, |  | ||||||
| 			text.size()); |  | ||||||
| 		_insertedTagsAreFromMime = true; |  | ||||||
| 	} else { |  | ||||||
| 			_insertedTags.clear(); | 			_insertedTags.clear(); | ||||||
|  | 			return source->text(); | ||||||
| 		} | 		} | ||||||
|  | 		auto result = QString::fromUtf8(source->data(textMime)); | ||||||
|  | 		_insertedTags = TextUtilities::DeserializeTags( | ||||||
|  | 			source->data(tagsMime), | ||||||
|  | 			result.size()); | ||||||
|  | 		_insertedTagsAreFromMime = true; | ||||||
|  | 		return result; | ||||||
|  | 	}(); | ||||||
| 	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