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