Copy text with expanded links only to external.

Paste valid custom links in message field if copied from messages.
This commit is contained in:
John Preston 2019-04-08 19:10:06 +04:00
parent 0f0c3b7461
commit b5be6df5e2
64 changed files with 772 additions and 647 deletions

View File

@ -486,8 +486,7 @@ DeleteMessagesBox::DeleteMessagesBox(
void DeleteMessagesBox::prepare() { void DeleteMessagesBox::prepare() {
auto details = TextWithEntities(); auto details = TextWithEntities();
const auto appendDetails = [&](TextWithEntities &&text) { const auto appendDetails = [&](TextWithEntities &&text) {
TextUtilities::Append(details, { "\n\n" }); details.append(qstr("\n\n")).append(std::move(text));
TextUtilities::Append(details, std::move(text));
}; };
auto deleteKey = lng_box_delete; auto deleteKey = lng_box_delete;
auto deleteStyle = &st::defaultBoxButton; auto deleteStyle = &st::defaultBoxButton;
@ -648,7 +647,7 @@ auto DeleteMessagesBox::revokeText(not_null<PeerData*> peer) const
if (const auto user = peer->asUser()) { if (const auto user = peer->asUser()) {
auto boldName = TextWithEntities{ user->firstName }; auto boldName = TextWithEntities{ user->firstName };
boldName.entities.push_back( boldName.entities.push_back(
EntityInText(EntityInTextBold, 0, boldName.text.size())); { EntityType::Bold, 0, boldName.text.size() });
if (canRevokeOutgoingCount == 1) { if (canRevokeOutgoingCount == 1) {
result.description = lng_selected_unsend_about_user_one__generic<TextWithEntities>( result.description = lng_selected_unsend_about_user_one__generic<TextWithEntities>(
lt_user, lt_user,

View File

@ -229,7 +229,7 @@ void ConfirmPhoneBox::prepare() {
aboutText.text = lng_confirm_phone_about(lt_phone, formattedPhone); aboutText.text = lng_confirm_phone_about(lt_phone, formattedPhone);
auto phonePosition = aboutText.text.indexOf(formattedPhone); auto phonePosition = aboutText.text.indexOf(formattedPhone);
if (phonePosition >= 0) { if (phonePosition >= 0) {
aboutText.entities.push_back(EntityInText(EntityInTextBold, phonePosition, formattedPhone.size())); aboutText.entities.push_back({ EntityType::Bold, phonePosition, formattedPhone.size() });
} }
_about->setMarkedText(aboutText); _about->setMarkedText(aboutText);

View File

@ -627,11 +627,11 @@ void Controller::refreshEditInviteLink() {
if (text.text.startsWith(remove)) { if (text.text.startsWith(remove)) {
text.text.remove(0, remove.size()); text.text.remove(0, remove.size());
} }
text.entities.push_back(EntityInText( text.entities.push_back({
EntityInTextCustomUrl, EntityType::CustomUrl,
0, 0,
text.text.size(), text.text.size(),
link)); link });
} }
_controls.inviteLink->setMarkedText(text); _controls.inviteLink->setMarkedText(text);

View File

@ -197,25 +197,25 @@ EntitiesInText ConvertTextTagsToEntities(const TextWithTags::Tags &tags) {
result.reserve(tags.size()); result.reserve(tags.size());
for (const auto &tag : tags) { for (const auto &tag : tags) {
const auto push = [&]( const auto push = [&](
EntityInTextType type, EntityType type,
const QString &data = QString()) { const QString &data = QString()) {
result.push_back( result.push_back(
EntityInText(type, tag.offset, tag.length, data)); EntityInText(type, tag.offset, tag.length, data));
}; };
if (IsMentionLink(tag.id)) { if (IsMentionLink(tag.id)) {
if (auto match = qthelp::regex_match("^(\\d+\\.\\d+)(/|$)", tag.id.midRef(kMentionTagStart.size()))) { if (auto match = qthelp::regex_match("^(\\d+\\.\\d+)(/|$)", tag.id.midRef(kMentionTagStart.size()))) {
push(EntityInTextMentionName, match->captured(1)); push(EntityType::MentionName, match->captured(1));
} }
} else if (tag.id == Ui::InputField::kTagBold) { } else if (tag.id == Ui::InputField::kTagBold) {
push(EntityInTextBold); push(EntityType::Bold);
} else if (tag.id == Ui::InputField::kTagItalic) { } else if (tag.id == Ui::InputField::kTagItalic) {
push(EntityInTextItalic); push(EntityType::Italic);
} else if (tag.id == Ui::InputField::kTagCode) { } else if (tag.id == Ui::InputField::kTagCode) {
push(EntityInTextCode); push(EntityType::Code);
} else if (tag.id == Ui::InputField::kTagPre) { } else if (tag.id == Ui::InputField::kTagPre) {
push(EntityInTextPre); push(EntityType::Pre);
} else /*if (ValidateUrl(tag.id)) */{ // We validate when we insert. } else /*if (ValidateUrl(tag.id)) */{ // We validate when we insert.
push(EntityInTextCustomUrl, tag.id); push(EntityType::CustomUrl, tag.id);
} }
} }
return result; return result;
@ -233,41 +233,44 @@ TextWithTags::Tags ConvertEntitiesToTextTags(const EntitiesInText &entities) {
result.push_back({ entity.offset(), entity.length(), tag }); result.push_back({ entity.offset(), entity.length(), tag });
}; };
switch (entity.type()) { switch (entity.type()) {
case EntityInTextMentionName: { case EntityType::MentionName: {
auto match = QRegularExpression("^(\\d+\\.\\d+)$").match(entity.data()); auto match = QRegularExpression(R"(^(\d+\.\d+)$)").match(entity.data());
if (match.hasMatch()) { if (match.hasMatch()) {
push(kMentionTagStart + entity.data()); push(kMentionTagStart + entity.data());
} }
} break; } break;
case EntityInTextCustomUrl: { case EntityType::CustomUrl: {
const auto url = entity.data(); const auto url = entity.data();
if (Ui::InputField::IsValidMarkdownLink(url) if (Ui::InputField::IsValidMarkdownLink(url)
&& !IsMentionLink(url)) { && !IsMentionLink(url)) {
push(url); push(url);
} }
} break; } break;
case EntityInTextBold: push(Ui::InputField::kTagBold); break; case EntityType::Bold: push(Ui::InputField::kTagBold); break;
case EntityInTextItalic: push(Ui::InputField::kTagItalic); break; case EntityType::Italic: push(Ui::InputField::kTagItalic); break;
case EntityInTextCode: push(Ui::InputField::kTagCode); break; case EntityType::Code: push(Ui::InputField::kTagCode); break;
case EntityInTextPre: push(Ui::InputField::kTagPre); break; case EntityType::Pre: push(Ui::InputField::kTagPre); break;
} }
} }
return result; return result;
} }
std::unique_ptr<QMimeData> MimeDataFromTextWithEntities( std::unique_ptr<QMimeData> MimeDataFromText(
const TextWithEntities &forClipboard) { const TextForMimeData &text) {
if (forClipboard.text.isEmpty()) { if (text.empty()) {
return nullptr; return nullptr;
} }
auto result = std::make_unique<QMimeData>(); auto result = std::make_unique<QMimeData>();
result->setText(forClipboard.text); result->setText(text.expanded);
auto tags = ConvertEntitiesToTextTags(forClipboard.entities); auto tags = ConvertEntitiesToTextTags(text.rich.entities);
if (!tags.isEmpty()) { if (!tags.isEmpty()) {
for (auto &tag : tags) { for (auto &tag : tags) {
tag.id = ConvertTagToMimeTag(tag.id); tag.id = ConvertTagToMimeTag(tag.id);
} }
result->setData(
TextUtilities::TagsTextMimeType(),
text.rich.text.toUtf8());
result->setData( result->setData(
TextUtilities::TagsMimeType(), TextUtilities::TagsMimeType(),
TextUtilities::SerializeTags(tags)); TextUtilities::SerializeTags(tags));
@ -275,10 +278,10 @@ std::unique_ptr<QMimeData> MimeDataFromTextWithEntities(
return result; return result;
} }
void SetClipboardWithEntities( void SetClipboardText(
const TextWithEntities &forClipboard, const TextForMimeData &text,
QClipboard::Mode mode) { QClipboard::Mode mode) {
if (auto data = MimeDataFromTextWithEntities(forClipboard)) { if (auto data = MimeDataFromText(text)) {
QApplication::clipboard()->setMimeData(data.release(), mode); QApplication::clipboard()->setMimeData(data.release(), mode);
} }
} }

View File

@ -21,10 +21,9 @@ QString PrepareMentionTag(not_null<UserData*> user);
EntitiesInText ConvertTextTagsToEntities(const TextWithTags::Tags &tags); EntitiesInText ConvertTextTagsToEntities(const TextWithTags::Tags &tags);
TextWithTags::Tags ConvertEntitiesToTextTags( TextWithTags::Tags ConvertEntitiesToTextTags(
const EntitiesInText &entities); const EntitiesInText &entities);
std::unique_ptr<QMimeData> MimeDataFromTextWithEntities( std::unique_ptr<QMimeData> MimeDataFromText(const TextForMimeData &text);
const TextWithEntities &forClipboard); void SetClipboardText(
void SetClipboardWithEntities( const TextForMimeData &text,
const TextWithEntities &forClipboard,
QClipboard::Mode mode = QClipboard::Clipboard); QClipboard::Mode mode = QClipboard::Clipboard);
Fn<bool( Fn<bool(

View File

@ -50,12 +50,6 @@ bool ClickHandler::setActive(const ClickHandlerPtr &p, ClickHandlerHost *host) {
return true; return true;
} }
TextWithEntities ClickHandler::getExpandedLinkTextWithEntities(int entityOffset, const QStringRef &textPart) const { auto ClickHandler::getTextEntity() const -> TextEntity {
return { QString(), EntitiesInText() }; return { EntityType::Invalid };
}
TextWithEntities ClickHandler::simpleTextWithEntity(const EntityInText &entity) const {
TextWithEntities result;
result.entities.push_back(entity);
return result;
} }

View File

@ -15,11 +15,6 @@ struct ClickContext {
QVariant other; QVariant other;
}; };
enum ExpandLinksMode {
ExpandLinksShortened,
ExpandLinksAll,
};
class ClickHandlerHost { class ClickHandlerHost {
protected: protected:
virtual void clickHandlerActiveChanged(const ClickHandlerPtr &action, bool active) { virtual void clickHandlerActiveChanged(const ClickHandlerPtr &action, bool active) {
@ -31,8 +26,7 @@ protected:
}; };
class EntityInText; enum class EntityType;
struct TextWithEntities;
class ClickHandler { class ClickHandler {
public: public:
virtual ~ClickHandler() { virtual ~ClickHandler() {
@ -59,9 +53,11 @@ public:
} }
// Entities in text support. // Entities in text support.
struct TextEntity {
// This method returns empty string if just textPart should be used (nothing to expand). EntityType type = EntityType();
virtual TextWithEntities getExpandedLinkTextWithEntities(int entityOffset, const QStringRef &textPart) const; QString data;
};
virtual TextEntity getTextEntity() const;
// This method should be called on mouse over a click handler. // This method should be called on mouse over a click handler.
// It returns true if the active handler was changed or false otherwise. // It returns true if the active handler was changed or false otherwise.
@ -146,11 +142,6 @@ public:
} }
} }
protected:
// For click handlers like mention or hashtag in getExpandedLinkTextWithEntities()
// we return just an empty string ("use original string part") with single entity.
TextWithEntities simpleTextWithEntity(const EntityInText &entity) const;
private: private:
static NeverFreedPointer<ClickHandlerPtr> _active; static NeverFreedPointer<ClickHandlerPtr> _active;
static NeverFreedPointer<ClickHandlerPtr> _pressed; static NeverFreedPointer<ClickHandlerPtr> _pressed;

View File

@ -133,15 +133,11 @@ void UrlClickHandler::Open(QString url, QVariant context) {
} }
} }
TextWithEntities UrlClickHandler::getExpandedLinkTextWithEntities(int entityOffset, const QStringRef &textPart) const { auto UrlClickHandler::getTextEntity() const -> TextEntity {
auto result = TextWithEntities(); const auto type = isEmail(_originalUrl)
result.text = _originalUrl; ? EntityType::Email
const auto entityLength = _originalUrl.size(); : EntityType::Url;
const auto entityType = isEmail(_originalUrl) return { type, _originalUrl };
? EntityInTextEmail
: EntityInTextUrl;
result.entities.push_back({ entityType, entityOffset, entityLength });
return result;
} }
void HiddenUrlClickHandler::Open(QString url, QVariant context) { void HiddenUrlClickHandler::Open(QString url, QVariant context) {
@ -200,8 +196,8 @@ void BotGameUrlClickHandler::onClick(ClickContext context) const {
} }
} }
TextWithEntities HiddenUrlClickHandler::getExpandedLinkTextWithEntities(int entityOffset, const QStringRef &textPart) const { auto HiddenUrlClickHandler::getTextEntity() const -> TextEntity {
return simpleTextWithEntity({ EntityInTextCustomUrl, entityOffset, textPart.size(), url() }); return { EntityType::CustomUrl, url() };
} }
QString MentionClickHandler::copyToClipboardContextItemText() const { QString MentionClickHandler::copyToClipboardContextItemText() const {
@ -215,8 +211,8 @@ void MentionClickHandler::onClick(ClickContext context) const {
} }
} }
TextWithEntities MentionClickHandler::getExpandedLinkTextWithEntities(int entityOffset, const QStringRef &textPart) const { auto MentionClickHandler::getTextEntity() const -> TextEntity {
return simpleTextWithEntity({ EntityInTextMention, entityOffset, textPart.size() }); return { EntityType::Mention };
} }
void MentionNameClickHandler::onClick(ClickContext context) const { void MentionNameClickHandler::onClick(ClickContext context) const {
@ -228,9 +224,9 @@ void MentionNameClickHandler::onClick(ClickContext context) const {
} }
} }
TextWithEntities MentionNameClickHandler::getExpandedLinkTextWithEntities(int entityOffset, const QStringRef &textPart) const { auto MentionNameClickHandler::getTextEntity() const -> TextEntity {
auto data = QString::number(_userId) + '.' + QString::number(_accessHash); auto data = QString::number(_userId) + '.' + QString::number(_accessHash);
return simpleTextWithEntity({ EntityInTextMentionName, entityOffset, textPart.size(), data }); return { EntityType::MentionName, data };
} }
QString MentionNameClickHandler::tooltip() const { QString MentionNameClickHandler::tooltip() const {
@ -254,8 +250,8 @@ void HashtagClickHandler::onClick(ClickContext context) const {
} }
} }
TextWithEntities HashtagClickHandler::getExpandedLinkTextWithEntities(int entityOffset, const QStringRef &textPart) const { auto HashtagClickHandler::getTextEntity() const -> TextEntity {
return simpleTextWithEntity({ EntityInTextHashtag, entityOffset, textPart.size() }); return { EntityType::Hashtag };
} }
QString CashtagClickHandler::copyToClipboardContextItemText() const { QString CashtagClickHandler::copyToClipboardContextItemText() const {
@ -269,10 +265,8 @@ void CashtagClickHandler::onClick(ClickContext context) const {
} }
} }
TextWithEntities CashtagClickHandler::getExpandedLinkTextWithEntities( auto CashtagClickHandler::getTextEntity() const -> TextEntity {
int entityOffset, return { EntityType::Cashtag };
const QStringRef &textPart) const {
return simpleTextWithEntity({ EntityInTextCashtag, entityOffset, textPart.size() });
} }
PeerData *BotCommandClickHandler::_peer = nullptr; PeerData *BotCommandClickHandler::_peer = nullptr;
@ -304,6 +298,6 @@ void BotCommandClickHandler::onClick(ClickContext context) const {
} }
} }
TextWithEntities BotCommandClickHandler::getExpandedLinkTextWithEntities(int entityOffset, const QStringRef &textPart) const { auto BotCommandClickHandler::getTextEntity() const -> TextEntity {
return simpleTextWithEntity({ EntityInTextBotCommand, entityOffset, textPart.size() }); return { EntityType::BotCommand };
} }

View File

@ -47,9 +47,7 @@ public:
return url(); return url();
} }
TextWithEntities getExpandedLinkTextWithEntities( TextEntity getTextEntity() const override;
int entityOffset,
const QStringRef &textPart) const override;
static void Open(QString url, QVariant context = {}); static void Open(QString url, QVariant context = {});
void onClick(ClickContext context) const override { void onClick(ClickContext context) const override {
@ -96,9 +94,7 @@ public:
} }
} }
TextWithEntities getExpandedLinkTextWithEntities( TextEntity getTextEntity() const override;
int entityOffset,
const QStringRef &textPart) const override;
}; };
@ -128,9 +124,7 @@ public:
QString copyToClipboardContextItemText() const override; QString copyToClipboardContextItemText() const override;
TextWithEntities getExpandedLinkTextWithEntities( TextEntity getTextEntity() const override;
int entityOffset,
const QStringRef &textPart) const override;
protected: protected:
QString url() const override { QString url() const override {
@ -152,9 +146,7 @@ public:
void onClick(ClickContext context) const override; void onClick(ClickContext context) const override;
TextWithEntities getExpandedLinkTextWithEntities( TextEntity getTextEntity() const override;
int entityOffset,
const QStringRef &textPart) const override;
QString tooltip() const override; QString tooltip() const override;
@ -178,9 +170,7 @@ public:
QString copyToClipboardContextItemText() const override; QString copyToClipboardContextItemText() const override;
TextWithEntities getExpandedLinkTextWithEntities( TextEntity getTextEntity() const override;
int entityOffset,
const QStringRef &textPart) const override;
protected: protected:
QString url() const override { QString url() const override {
@ -205,9 +195,7 @@ public:
QString copyToClipboardContextItemText() const override; QString copyToClipboardContextItemText() const override;
TextWithEntities getExpandedLinkTextWithEntities( TextEntity getTextEntity() const override;
int entityOffset,
const QStringRef &textPart) const override;
protected: protected:
QString url() const override { QString url() const override {
@ -239,9 +227,7 @@ public:
_bot = bot; _bot = bot;
} }
TextWithEntities getExpandedLinkTextWithEntities( TextEntity getTextEntity() const override;
int entityOffset,
const QStringRef &textPart) const override;
protected: protected:
QString url() const override { QString url() const override {

View File

@ -120,15 +120,14 @@ QString WithCaptionNotificationText(
} // namespace } // namespace
TextWithEntities WithCaptionClipboardText( TextForMimeData WithCaptionClipboardText(
const QString &attachType, const QString &attachType,
TextWithEntities &&caption) { TextForMimeData &&caption) {
TextWithEntities result; auto result = TextForMimeData();
result.text.reserve(5 + attachType.size() + caption.text.size()); result.reserve(5 + attachType.size() + caption.expanded.size());
result.text.append(qstr("[ ")).append(attachType).append(qstr(" ]")); result.append(qstr("[ ")).append(attachType).append(qstr(" ]"));
if (!caption.text.isEmpty()) { if (!caption.empty()) {
result.text.append(qstr("\n")); result.append('\n').append(std::move(caption));
TextUtilities::Append(result, std::move(caption));
} }
return result; return result;
} }
@ -321,7 +320,7 @@ QString MediaPhoto::pinnedTextSubstring() const {
return lang(lng_action_pinned_media_photo); return lang(lng_action_pinned_media_photo);
} }
TextWithEntities MediaPhoto::clipboardText() const { TextForMimeData MediaPhoto::clipboardText() const {
return WithCaptionClipboardText( return WithCaptionClipboardText(
lang(lng_in_dlg_photo), lang(lng_in_dlg_photo),
parent()->clipboardText()); parent()->clipboardText());
@ -629,7 +628,7 @@ QString MediaFile::pinnedTextSubstring() const {
return lang(lng_action_pinned_media_file); return lang(lng_action_pinned_media_file);
} }
TextWithEntities MediaFile::clipboardText() const { TextForMimeData MediaFile::clipboardText() const {
const auto attachType = [&] { const auto attachType = [&] {
const auto name = _document->composeNameString(); const auto name = _document->composeNameString();
const auto addName = !name.isEmpty() const auto addName = !name.isEmpty()
@ -816,7 +815,7 @@ QString MediaContact::pinnedTextSubstring() const {
return lang(lng_action_pinned_media_contact); return lang(lng_action_pinned_media_contact);
} }
TextWithEntities MediaContact::clipboardText() const { TextForMimeData MediaContact::clipboardText() const {
const auto text = qsl("[ ") + lang(lng_in_dlg_contact) + qsl(" ]\n") const auto text = qsl("[ ") + lang(lng_in_dlg_contact) + qsl(" ]\n")
+ lng_full_name( + lng_full_name(
lt_first_name, lt_first_name,
@ -825,7 +824,7 @@ TextWithEntities MediaContact::clipboardText() const {
_contact.lastName).trimmed() _contact.lastName).trimmed()
+ '\n' + '\n'
+ _contact.phoneNumber; + _contact.phoneNumber;
return { text, EntitiesInText() }; return TextForMimeData::Simple(text);
} }
bool MediaContact::updateInlineResultMedia(const MTPMessageMedia &media) { bool MediaContact::updateInlineResultMedia(const MTPMessageMedia &media) {
@ -900,26 +899,22 @@ QString MediaLocation::pinnedTextSubstring() const {
return lang(lng_action_pinned_media_location); return lang(lng_action_pinned_media_location);
} }
TextWithEntities MediaLocation::clipboardText() const { TextForMimeData MediaLocation::clipboardText() const {
TextWithEntities result = { auto result = TextForMimeData::Simple(
qsl("[ ") + lang(lng_maps_point) + qsl(" ]\n"), qstr("[ ") + lang(lng_maps_point) + qstr(" ]\n"));
EntitiesInText()
};
auto titleResult = TextUtilities::ParseEntities( auto titleResult = TextUtilities::ParseEntities(
TextUtilities::Clean(_title), TextUtilities::Clean(_title),
Ui::WebpageTextTitleOptions().flags); Ui::WebpageTextTitleOptions().flags);
auto descriptionResult = TextUtilities::ParseEntities( auto descriptionResult = TextUtilities::ParseEntities(
TextUtilities::Clean(_description), TextUtilities::Clean(_description),
TextParseLinks | TextParseMultiline | TextParseRichText); TextParseLinks | TextParseMultiline | TextParseRichText);
if (!titleResult.text.isEmpty()) { if (!titleResult.empty()) {
TextUtilities::Append(result, std::move(titleResult)); result.append(std::move(titleResult));
result.text.append('\n');
} }
if (!descriptionResult.text.isEmpty()) { if (!descriptionResult.text.isEmpty()) {
TextUtilities::Append(result, std::move(descriptionResult)); result.append(std::move(descriptionResult));
result.text.append('\n');
} }
result.text += LocationClickHandler(_location->coords).dragText(); result.append(LocationClickHandler(_location->coords).dragText());
return result; return result;
} }
@ -972,8 +967,9 @@ QString MediaCall::pinnedTextSubstring() const {
return QString(); return QString();
} }
TextWithEntities MediaCall::clipboardText() const { TextForMimeData MediaCall::clipboardText() const {
return { qsl("[ ") + notificationText() + qsl(" ]"), EntitiesInText() }; return TextForMimeData::Simple(
qstr("[ ") + notificationText() + qstr(" ]"));
} }
bool MediaCall::allowsForward() const { bool MediaCall::allowsForward() const {
@ -1071,8 +1067,8 @@ QString MediaWebPage::pinnedTextSubstring() const {
return QString(); return QString();
} }
TextWithEntities MediaWebPage::clipboardText() const { TextForMimeData MediaWebPage::clipboardText() const {
return TextWithEntities(); return TextForMimeData();
} }
bool MediaWebPage::allowsEdit() const { bool MediaWebPage::allowsEdit() const {
@ -1145,8 +1141,8 @@ QString MediaGame::pinnedTextSubstring() const {
return lng_action_pinned_media_game(lt_game, title); return lng_action_pinned_media_game(lt_game, title);
} }
TextWithEntities MediaGame::clipboardText() const { TextForMimeData MediaGame::clipboardText() const {
return TextWithEntities(); return TextForMimeData();
} }
QString MediaGame::errorTextForForward(not_null<PeerData*> peer) const { QString MediaGame::errorTextForForward(not_null<PeerData*> peer) const {
@ -1228,8 +1224,8 @@ QString MediaInvoice::pinnedTextSubstring() const {
return QString(); return QString();
} }
TextWithEntities MediaInvoice::clipboardText() const { TextForMimeData MediaInvoice::clipboardText() const {
return TextWithEntities(); return TextForMimeData();
} }
bool MediaInvoice::updateInlineResultMedia(const MTPMessageMedia &media) { bool MediaInvoice::updateInlineResultMedia(const MTPMessageMedia &media) {
@ -1272,12 +1268,12 @@ QString MediaPoll::pinnedTextSubstring() const {
return QChar(171) + _poll->question + QChar(187); return QChar(171) + _poll->question + QChar(187);
} }
TextWithEntities MediaPoll::clipboardText() const { TextForMimeData MediaPoll::clipboardText() const {
const auto text = qsl("[ ") const auto text = qstr("[ ")
+ lang(lng_in_dlg_poll) + lang(lng_in_dlg_poll)
+ qsl(" : ") + qstr(" : ")
+ _poll->question + _poll->question
+ qsl(" ]") + qstr(" ]")
+ ranges::accumulate( + ranges::accumulate(
ranges::view::all( ranges::view::all(
_poll->answers _poll->answers
@ -1285,7 +1281,7 @@ TextWithEntities MediaPoll::clipboardText() const {
return "\n- " + answer.text; return "\n- " + answer.text;
}), }),
QString()); QString());
return { text, EntitiesInText() }; return TextForMimeData::Simple(text);
} }
QString MediaPoll::errorTextForForward(not_null<PeerData*> peer) const { QString MediaPoll::errorTextForForward(not_null<PeerData*> peer) const {

View File

@ -89,7 +89,7 @@ public:
virtual QString chatListText() const; virtual QString chatListText() const;
virtual QString notificationText() const = 0; virtual QString notificationText() const = 0;
virtual QString pinnedTextSubstring() const = 0; virtual QString pinnedTextSubstring() const = 0;
virtual TextWithEntities clipboardText() const = 0; virtual TextForMimeData clipboardText() const = 0;
virtual bool allowsForward() const; virtual bool allowsForward() const;
virtual bool allowsEdit() const; virtual bool allowsEdit() const;
virtual bool allowsEditCaption() const; virtual bool allowsEditCaption() const;
@ -140,7 +140,7 @@ public:
QString chatListText() const override; QString chatListText() const override;
QString notificationText() const override; QString notificationText() const override;
QString pinnedTextSubstring() const override; QString pinnedTextSubstring() const override;
TextWithEntities clipboardText() const override; TextForMimeData clipboardText() const override;
bool allowsEditCaption() const override; bool allowsEditCaption() const override;
bool allowsEditMedia() const override; bool allowsEditMedia() const override;
QString errorTextForForward(not_null<PeerData*> peer) const override; QString errorTextForForward(not_null<PeerData*> peer) const override;
@ -176,7 +176,7 @@ public:
QString chatListText() const override; QString chatListText() const override;
QString notificationText() const override; QString notificationText() const override;
QString pinnedTextSubstring() const override; QString pinnedTextSubstring() const override;
TextWithEntities clipboardText() const override; TextForMimeData clipboardText() const override;
bool allowsEditCaption() const override; bool allowsEditCaption() const override;
bool allowsEditMedia() const override; bool allowsEditMedia() const override;
bool forwardedBecomesUnread() const override; bool forwardedBecomesUnread() const override;
@ -209,7 +209,7 @@ public:
const SharedContact *sharedContact() const override; const SharedContact *sharedContact() const override;
QString notificationText() const override; QString notificationText() const override;
QString pinnedTextSubstring() const override; QString pinnedTextSubstring() const override;
TextWithEntities clipboardText() const override; TextForMimeData clipboardText() const override;
bool updateInlineResultMedia(const MTPMessageMedia &media) override; bool updateInlineResultMedia(const MTPMessageMedia &media) override;
bool updateSentMedia(const MTPMessageMedia &media) override; bool updateSentMedia(const MTPMessageMedia &media) override;
@ -239,7 +239,7 @@ public:
QString chatListText() const override; QString chatListText() const override;
QString notificationText() const override; QString notificationText() const override;
QString pinnedTextSubstring() const override; QString pinnedTextSubstring() const override;
TextWithEntities clipboardText() const override; TextForMimeData clipboardText() const override;
bool updateInlineResultMedia(const MTPMessageMedia &media) override; bool updateInlineResultMedia(const MTPMessageMedia &media) override;
bool updateSentMedia(const MTPMessageMedia &media) override; bool updateSentMedia(const MTPMessageMedia &media) override;
@ -265,7 +265,7 @@ public:
const Call *call() const override; const Call *call() const override;
QString notificationText() const override; QString notificationText() const override;
QString pinnedTextSubstring() const override; QString pinnedTextSubstring() const override;
TextWithEntities clipboardText() const override; TextForMimeData clipboardText() const override;
bool allowsForward() const override; bool allowsForward() const override;
bool allowsRevoke() const override; bool allowsRevoke() const override;
@ -302,7 +302,7 @@ public:
QString chatListText() const override; QString chatListText() const override;
QString notificationText() const override; QString notificationText() const override;
QString pinnedTextSubstring() const override; QString pinnedTextSubstring() const override;
TextWithEntities clipboardText() const override; TextForMimeData clipboardText() const override;
bool allowsEdit() const override; bool allowsEdit() const override;
bool updateInlineResultMedia(const MTPMessageMedia &media) override; bool updateInlineResultMedia(const MTPMessageMedia &media) override;
@ -330,7 +330,7 @@ public:
Image *replyPreview() const override; Image *replyPreview() const override;
QString notificationText() const override; QString notificationText() const override;
QString pinnedTextSubstring() const override; QString pinnedTextSubstring() const override;
TextWithEntities clipboardText() const override; TextForMimeData clipboardText() const override;
QString errorTextForForward(not_null<PeerData*> peer) const override; QString errorTextForForward(not_null<PeerData*> peer) const override;
bool consumeMessageText(const TextWithEntities &text) override; bool consumeMessageText(const TextWithEntities &text) override;
@ -365,7 +365,7 @@ public:
Image *replyPreview() const override; Image *replyPreview() const override;
QString notificationText() const override; QString notificationText() const override;
QString pinnedTextSubstring() const override; QString pinnedTextSubstring() const override;
TextWithEntities clipboardText() const override; TextForMimeData clipboardText() const override;
bool updateInlineResultMedia(const MTPMessageMedia &media) override; bool updateInlineResultMedia(const MTPMessageMedia &media) override;
bool updateSentMedia(const MTPMessageMedia &media) override; bool updateSentMedia(const MTPMessageMedia &media) override;
@ -391,7 +391,7 @@ public:
QString notificationText() const override; QString notificationText() const override;
QString pinnedTextSubstring() const override; QString pinnedTextSubstring() const override;
TextWithEntities clipboardText() const override; TextForMimeData clipboardText() const override;
QString errorTextForForward(not_null<PeerData*> peer) const override; QString errorTextForForward(not_null<PeerData*> peer) const override;
bool updateInlineResultMedia(const MTPMessageMedia &media) override; bool updateInlineResultMedia(const MTPMessageMedia &media) override;
@ -405,8 +405,8 @@ private:
}; };
TextWithEntities WithCaptionClipboardText( TextForMimeData WithCaptionClipboardText(
const QString &attachType, const QString &attachType,
TextWithEntities &&caption); TextForMimeData &&caption);
} // namespace Data } // namespace Data

View File

@ -253,11 +253,11 @@ void SettingsWidget::addLocationLabel(
QDir::toNativeSeparators(text), QDir::toNativeSeparators(text),
EntitiesInText() EntitiesInText()
}; };
pathLink.entities.push_back(EntityInText( pathLink.entities.push_back({
EntityInTextCustomUrl, EntityType::CustomUrl,
0, 0,
text.size(), text.size(),
QString("internal:edit_export_path"))); QString("internal:edit_export_path") });
return lng_export_option_location__generic<TextWithEntities>( return lng_export_option_location__generic<TextWithEntities>(
lt_path, lt_path,
pathLink); pathLink);
@ -288,17 +288,17 @@ void SettingsWidget::addLimitsLabel(
? langDayOfMonthFull(ParseDateTime(till).date()) ? langDayOfMonthFull(ParseDateTime(till).date())
: lang(lng_export_end); : lang(lng_export_end);
auto fromLink = TextWithEntities{ begin }; auto fromLink = TextWithEntities{ begin };
fromLink.entities.push_back(EntityInText( fromLink.entities.push_back({
EntityInTextCustomUrl, EntityType::CustomUrl,
0, 0,
begin.size(), begin.size(),
QString("internal:edit_from"))); QString("internal:edit_from") });
auto tillLink = TextWithEntities{ end }; auto tillLink = TextWithEntities{ end };
tillLink.entities.push_back(EntityInText( tillLink.entities.push_back({
EntityInTextCustomUrl, EntityType::CustomUrl,
0, 0,
end.size(), end.size(),
QString("internal:edit_till"))); QString("internal:edit_till") });
return lng_export_limits__generic<TextWithEntities>( return lng_export_limits__generic<TextWithEntities>(
lt_from, lt_from,
fromLink, fromLink,

View File

@ -451,7 +451,7 @@ void InnerWidget::updateEmptyText() {
auto hasSearch = !_searchQuery.isEmpty(); auto hasSearch = !_searchQuery.isEmpty();
auto hasFilter = (_filter.flags != 0) || !_filter.allUsers; auto hasFilter = (_filter.flags != 0) || !_filter.allUsers;
auto text = TextWithEntities { lang((hasSearch || hasFilter) ? lng_admin_log_no_results_title : lng_admin_log_no_events_title) }; auto text = TextWithEntities { lang((hasSearch || hasFilter) ? lng_admin_log_no_results_title : lng_admin_log_no_events_title) };
text.entities.append(EntityInText(EntityInTextBold, 0, text.text.size())); text.entities.append(EntityInText(EntityType::Bold, 0, text.text.size()));
auto description = hasSearch auto description = hasSearch
? lng_admin_log_no_results_search_text(lt_query, TextUtilities::Clean(_searchQuery)) ? lng_admin_log_no_results_search_text(lt_query, TextUtilities::Clean(_searchQuery))
: lang(hasFilter ? lng_admin_log_no_results_text : lng_admin_log_no_events_text); : lang(hasFilter ? lng_admin_log_no_results_text : lng_admin_log_no_events_text);
@ -890,10 +890,10 @@ void InnerWidget::paintEmpty(Painter &p) {
_emptyText.draw(p, rect.x() + st::historyAdminLogEmptyPadding.left(), rect.y() + st::historyAdminLogEmptyPadding.top(), innerWidth, style::al_top); _emptyText.draw(p, rect.x() + st::historyAdminLogEmptyPadding.left(), rect.y() + st::historyAdminLogEmptyPadding.top(), innerWidth, style::al_top);
} }
TextWithEntities InnerWidget::getSelectedText() const { TextForMimeData InnerWidget::getSelectedText() const {
return _selectedItem return _selectedItem
? _selectedItem->selectedText(_selectedText) ? _selectedItem->selectedText(_selectedText)
: TextWithEntities(); : TextForMimeData();
} }
void InnerWidget::keyPressEvent(QKeyEvent *e) { void InnerWidget::keyPressEvent(QKeyEvent *e) {
@ -1105,7 +1105,7 @@ void InnerWidget::copyContextImage(PhotoData *photo) {
} }
void InnerWidget::copySelectedText() { void InnerWidget::copySelectedText() {
SetClipboardWithEntities(getSelectedText()); SetClipboardText(getSelectedText());
} }
void InnerWidget::showStickerPackInfo(not_null<DocumentData*> document) { void InnerWidget::showStickerPackInfo(not_null<DocumentData*> document) {
@ -1136,7 +1136,7 @@ void InnerWidget::openContextGif(FullMsgId itemId) {
void InnerWidget::copyContextText(FullMsgId itemId) { void InnerWidget::copyContextText(FullMsgId itemId) {
if (const auto item = App::histItemById(itemId)) { if (const auto item = App::histItemById(itemId)) {
SetClipboardWithEntities(HistoryItemText(item)); SetClipboardText(HistoryItemText(item));
} }
} }

View File

@ -153,7 +153,7 @@ private:
void openContextGif(FullMsgId itemId); void openContextGif(FullMsgId itemId);
void copyContextText(FullMsgId itemId); void copyContextText(FullMsgId itemId);
void copySelectedText(); void copySelectedText();
TextWithEntities getSelectedText() const; TextForMimeData getSelectedText() const;
void suggestRestrictUser(not_null<UserData*> user); void suggestRestrictUser(not_null<UserData*> user);
void restrictUser(not_null<UserData*> user, const MTPChatBannedRights &oldRights, const MTPChatBannedRights &newRights); void restrictUser(not_null<UserData*> user, const MTPChatBannedRights &oldRights, const MTPChatBannedRights &newRights);
void restrictUserDone(not_null<UserData*> user, const MTPChatBannedRights &rights); void restrictUserDone(not_null<UserData*> user, const MTPChatBannedRights &rights);

View File

@ -28,7 +28,10 @@ TextWithEntities PrepareText(const QString &value, const QString &emptyValue) {
if (result.text.isEmpty()) { if (result.text.isEmpty()) {
result.text = emptyValue; result.text = emptyValue;
if (!emptyValue.isEmpty()) { if (!emptyValue.isEmpty()) {
result.entities.push_back(EntityInText(EntityInTextItalic, 0, emptyValue.size())); result.entities.push_back({
EntityType::Italic,
0,
emptyValue.size() });
} }
} else { } else {
TextUtilities::ParseEntities(result, TextParseLinks | TextParseMentions | TextParseHashtags | TextParseBotCommands); TextUtilities::ParseEntities(result, TextParseLinks | TextParseMentions | TextParseHashtags | TextParseBotCommands);
@ -224,17 +227,20 @@ auto GenerateUserString(MTPint userId) {
auto entityData = QString::number(user->id) auto entityData = QString::number(user->id)
+ '.' + '.'
+ QString::number(user->accessHash()); + QString::number(user->accessHash());
name.entities.push_back(EntityInText( name.entities.push_back({
EntityInTextMentionName, EntityType::MentionName,
0, 0,
name.text.size(), name.text.size(),
entityData)); entityData });
auto username = user->userName(); auto username = user->userName();
if (username.isEmpty()) { if (username.isEmpty()) {
return name; return name;
} }
auto mention = TextWithEntities { '@' + username }; auto mention = TextWithEntities { '@' + username };
mention.entities.push_back(EntityInText(EntityInTextMention, 0, mention.text.size())); mention.entities.push_back({
EntityType::Mention,
0,
mention.text.size() });
return lng_admin_log_user_with_username__generic(lt_name, name, lt_mention, mention); return lng_admin_log_user_with_username__generic(lt_name, name, lt_mention, mention);
} }
@ -285,7 +291,7 @@ auto GenerateParticipantChangeTextInner(
TextWithEntities GenerateParticipantChangeText(not_null<ChannelData*> channel, const MTPChannelParticipant &participant, const MTPChannelParticipant *oldParticipant = nullptr) { TextWithEntities GenerateParticipantChangeText(not_null<ChannelData*> channel, const MTPChannelParticipant &participant, const MTPChannelParticipant *oldParticipant = nullptr) {
auto result = GenerateParticipantChangeTextInner(channel, participant, oldParticipant); auto result = GenerateParticipantChangeTextInner(channel, participant, oldParticipant);
result.entities.push_front(EntityInText(EntityInTextItalic, 0, result.text.size())); result.entities.push_front(EntityInText(EntityType::Italic, 0, result.text.size()));
return result; return result;
} }
@ -295,7 +301,7 @@ TextWithEntities GenerateDefaultBannedRightsChangeText(not_null<ChannelData*> ch
if (!changes.isEmpty()) { if (!changes.isEmpty()) {
result.text.append('\n' + changes); result.text.append('\n' + changes);
} }
result.entities.push_front(EntityInText(EntityInTextItalic, 0, result.text.size())); result.entities.push_front(EntityInText(EntityType::Italic, 0, result.text.size()));
return result; return result;
} }

View File

@ -1156,17 +1156,19 @@ std::unique_ptr<QMimeData> HistoryInner::prepareDrag() {
} }
} }
TextWithEntities sel; auto urls = QList<QUrl>();
QList<QUrl> urls; const auto selectedText = [&] {
if (uponSelected) { if (uponSelected) {
sel = getSelectedText(); return getSelectedText();
} else if (pressedHandler) { } else if (pressedHandler) {
sel = { pressedHandler->dragText(), EntitiesInText() }; //if (!sel.isEmpty() && sel.at(0) != '/' && sel.at(0) != '@' && sel.at(0) != '#') {
//if (!sel.isEmpty() && sel.at(0) != '/' && sel.at(0) != '@' && sel.at(0) != '#') { // urls.push_back(QUrl::fromEncoded(sel.toUtf8())); // Google Chrome crashes in Mac OS X O_o
// urls.push_back(QUrl::fromEncoded(sel.toUtf8())); // Google Chrome crashes in Mac OS X O_o //}
//} return TextForMimeData::Simple(pressedHandler->dragText());
} }
if (auto mimeData = MimeDataFromTextWithEntities(sel)) { return TextForMimeData();
}();
if (auto mimeData = MimeDataFromText(selectedText)) {
updateDragSelection(nullptr, nullptr, false); updateDragSelection(nullptr, nullptr, false);
_widget->noSelectingScroll(); _widget->noSelectingScroll();
@ -1789,7 +1791,7 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
} }
void HistoryInner::copySelectedText() { void HistoryInner::copySelectedText() {
SetClipboardWithEntities(getSelectedText()); SetClipboardText(getSelectedText());
} }
void HistoryInner::savePhotoToFile(not_null<PhotoData*> photo) { void HistoryInner::savePhotoToFile(not_null<PhotoData*> photo) {
@ -1864,9 +1866,9 @@ void HistoryInner::saveContextGif(FullMsgId itemId) {
void HistoryInner::copyContextText(FullMsgId itemId) { void HistoryInner::copyContextText(FullMsgId itemId) {
if (const auto item = App::histItemById(itemId)) { if (const auto item = App::histItemById(itemId)) {
if (const auto group = Auth().data().groups().find(item)) { if (const auto group = Auth().data().groups().find(item)) {
SetClipboardWithEntities(HistoryGroupText(group)); SetClipboardText(HistoryGroupText(group));
} else { } else {
SetClipboardWithEntities(HistoryItemText(item)); SetClipboardText(HistoryItemText(item));
} }
} }
} }
@ -1875,7 +1877,7 @@ void HistoryInner::resizeEvent(QResizeEvent *e) {
mouseActionUpdate(); mouseActionUpdate();
} }
TextWithEntities HistoryInner::getSelectedText() const { TextForMimeData HistoryInner::getSelectedText() const {
auto selected = _selected; auto selected = _selected;
if (_mouseAction == MouseAction::Selecting && _dragSelFrom && _dragSelTo) { if (_mouseAction == MouseAction::Selecting && _dragSelFrom && _dragSelTo) {
@ -1883,32 +1885,32 @@ TextWithEntities HistoryInner::getSelectedText() const {
} }
if (selected.empty()) { if (selected.empty()) {
return TextWithEntities(); return TextForMimeData();
} }
if (selected.cbegin()->second != FullSelection) { if (selected.cbegin()->second != FullSelection) {
const auto [item, selection] = *selected.cbegin(); const auto [item, selection] = *selected.cbegin();
if (const auto view = item->mainView()) { if (const auto view = item->mainView()) {
return view->selectedText(selection); return view->selectedText(selection);
} }
return TextWithEntities(); return TextForMimeData();
} }
const auto timeFormat = qsl(", [dd.MM.yy hh:mm]\n"); const auto timeFormat = qsl(", [dd.MM.yy hh:mm]\n");
auto groups = base::flat_set<not_null<const Data::Group*>>(); auto groups = base::flat_set<not_null<const Data::Group*>>();
auto fullSize = 0; auto fullSize = 0;
auto texts = base::flat_map<Data::MessagePosition, TextWithEntities>(); auto texts = base::flat_map<Data::MessagePosition, TextForMimeData>();
const auto wrapItem = [&]( const auto wrapItem = [&](
not_null<HistoryItem*> item, not_null<HistoryItem*> item,
TextWithEntities &&unwrapped) { TextForMimeData &&unwrapped) {
auto time = ItemDateTime(item).toString(timeFormat); auto time = ItemDateTime(item).toString(timeFormat);
auto part = TextWithEntities(); auto part = TextForMimeData();
auto size = item->author()->name.size() auto size = item->author()->name.size()
+ time.size() + time.size()
+ unwrapped.text.size(); + unwrapped.expanded.size();
part.text.reserve(size); part.reserve(size);
part.text.append(item->author()->name).append(time); part.append(item->author()->name).append(time);
TextUtilities::Append(part, std::move(unwrapped)); part.append(std::move(unwrapped));
texts.emplace(item->position(), part); texts.emplace(item->position(), part);
fullSize += size; fullSize += size;
}; };
@ -1937,13 +1939,13 @@ TextWithEntities HistoryInner::getSelectedText() const {
} }
} }
auto result = TextWithEntities(); auto result = TextForMimeData();
auto sep = qsl("\n\n"); const auto sep = qstr("\n\n");
result.text.reserve(fullSize + (texts.size() - 1) * sep.size()); result.reserve(fullSize + (texts.size() - 1) * sep.size());
for (auto i = texts.begin(), e = texts.end(); i != e;) { for (auto i = texts.begin(), e = texts.end(); i != e;) {
TextUtilities::Append(result, std::move(i->second)); result.append(std::move(i->second));
if (++i != e) { if (++i != e) {
result.text.append(sep); result.append(sep);
} }
} }
return result; return result;

View File

@ -55,7 +55,7 @@ public:
void messagesReceived(PeerData *peer, const QVector<MTPMessage> &messages); void messagesReceived(PeerData *peer, const QVector<MTPMessage> &messages);
void messagesReceivedDown(PeerData *peer, const QVector<MTPMessage> &messages); void messagesReceivedDown(PeerData *peer, const QVector<MTPMessage> &messages);
TextWithEntities getSelectedText() const; TextForMimeData getSelectedText() const;
void touchScrollUpdated(const QPoint &screenPos); void touchScrollUpdated(const QPoint &screenPos);

View File

@ -66,7 +66,7 @@ not_null<HistoryItem*> CreateUnsupportedMessage(
}; };
TextUtilities::ParseEntities(text, Ui::ItemTextNoMonoOptions().flags); TextUtilities::ParseEntities(text, Ui::ItemTextNoMonoOptions().flags);
text.entities.push_front( text.entities.push_front(
EntityInText(EntityInTextItalic, 0, text.text.size())); EntityInText(EntityType::Italic, 0, text.text.size()));
flags &= ~MTPDmessage::Flag::f_post_author; flags &= ~MTPDmessage::Flag::f_post_author;
flags |= MTPDmessage_ClientFlag::f_is_unsupported; flags |= MTPDmessage_ClientFlag::f_is_unsupported;
return new HistoryMessage( return new HistoryMessage(

View File

@ -220,10 +220,10 @@ public:
return inDialogsText(DrawInDialog::WithoutSender); return inDialogsText(DrawInDialog::WithoutSender);
} }
virtual TextWithEntities originalText() const { virtual TextWithEntities originalText() const {
return { QString(), EntitiesInText() }; return TextWithEntities();
} }
virtual TextWithEntities clipboardText() const { virtual TextForMimeData clipboardText() const {
return { QString(), EntitiesInText() }; return TextForMimeData();
} }
virtual void setViewsCount(int32 count) { virtual void setViewsCount(int32 count) {

View File

@ -16,50 +16,41 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
#include "ui/text_options.h" #include "ui/text_options.h"
TextWithEntities WrapAsReply( TextForMimeData WrapAsReply(
TextWithEntities &&text, TextForMimeData &&text,
not_null<HistoryItem*> to) { not_null<HistoryItem*> to) {
const auto name = to->author()->name; const auto name = to->author()->name;
auto result = TextWithEntities(); auto result = TextForMimeData();
result.text.reserve( result.reserve(
lang(lng_in_reply_to).size() lang(lng_in_reply_to).size()
+ name.size() + name.size()
+ 4 + 4
+ text.text.size()); + text.expanded.size());
result.text.append('[' return result.append('['
).append(lang(lng_in_reply_to) ).append(lang(lng_in_reply_to)
).append(' ' ).append(' '
).append(name ).append(name
).append(qsl("]\n") ).append(qstr("]\n")
); ).append(std::move(text));
TextUtilities::Append(result, std::move(text));
return result;
} }
TextWithEntities WrapAsForwarded( TextForMimeData WrapAsForwarded(
TextWithEntities &&text, TextForMimeData &&text,
not_null<HistoryMessageForwarded*> forwarded) { not_null<HistoryMessageForwarded*> forwarded) {
auto info = forwarded->text.toTextWithEntities( auto info = forwarded->text.toTextForMimeData();
AllTextSelection, auto result = TextForMimeData();
ExpandLinksAll); result.reserve(
auto result = TextWithEntities(); info.expanded.size() + 4 + text.expanded.size(),
result.text.reserve( info.rich.entities.size() + text.rich.entities.size());
info.text.size() return result.append('['
+ 4 ).append(std::move(info)
+ text.text.size()); ).append(qstr("]\n")
result.entities.reserve( ).append(std::move(text));
info.entities.size()
+ text.entities.size());
result.text.append('[');
TextUtilities::Append(result, std::move(info));
result.text.append(qsl("]\n"));
TextUtilities::Append(result, std::move(text));
return result;
} }
TextWithEntities WrapAsItem( TextForMimeData WrapAsItem(
not_null<HistoryItem*> item, not_null<HistoryItem*> item,
TextWithEntities &&result) { TextForMimeData &&result) {
if (const auto reply = item->Get<HistoryMessageReply>()) { if (const auto reply = item->Get<HistoryMessageReply>()) {
if (const auto message = reply->replyToMsg) { if (const auto message = reply->replyToMsg) {
result = WrapAsReply(std::move(result), message); result = WrapAsReply(std::move(result), message);
@ -71,65 +62,65 @@ TextWithEntities WrapAsItem(
return std::move(result); return std::move(result);
} }
TextWithEntities HistoryItemText(not_null<HistoryItem*> item) { TextForMimeData HistoryItemText(not_null<HistoryItem*> item) {
const auto media = item->media(); const auto media = item->media();
auto mediaResult = media ? media->clipboardText() : TextWithEntities(); auto mediaResult = media ? media->clipboardText() : TextForMimeData();
auto textResult = mediaResult.text.isEmpty() auto textResult = mediaResult.empty()
? item->clipboardText() ? item->clipboardText()
: TextWithEntities(); : TextForMimeData();
auto logEntryOriginalResult = [&] { auto logEntryOriginalResult = [&] {
const auto entry = item->Get<HistoryMessageLogEntryOriginal>(); const auto entry = item->Get<HistoryMessageLogEntryOriginal>();
if (!entry) { if (!entry) {
return TextWithEntities(); return TextForMimeData();
} }
const auto title = TextUtilities::SingleLine(entry->page->title.isEmpty() const auto title = TextUtilities::SingleLine(entry->page->title.isEmpty()
? entry->page->author ? entry->page->author
: entry->page->title); : entry->page->title);
auto titleResult = TextUtilities::ParseEntities( auto titleResult = TextForMimeData::Rich(
title, TextUtilities::ParseEntities(
Ui::WebpageTextTitleOptions().flags); title,
auto descriptionResult = entry->page->description; Ui::WebpageTextTitleOptions().flags));
if (titleResult.text.isEmpty()) { auto descriptionResult = TextForMimeData::Rich(
base::duplicate(entry->page->description));
if (titleResult.empty()) {
return descriptionResult; return descriptionResult;
} else if (descriptionResult.text.isEmpty()) { } else if (descriptionResult.empty()) {
return titleResult; return titleResult;
} }
titleResult.text += '\n'; titleResult.append('\n').append(std::move(descriptionResult));
TextUtilities::Append(titleResult, std::move(descriptionResult));
return titleResult; return titleResult;
}(); }();
auto result = textResult; auto result = textResult;
if (result.text.isEmpty()) { if (result.empty()) {
result = std::move(mediaResult); result = std::move(mediaResult);
} else if (!mediaResult.text.isEmpty()) { } else if (!mediaResult.empty()) {
result.text += qstr("\n\n"); result.append(qstr("\n\n")).append(std::move(mediaResult));
TextUtilities::Append(result, std::move(mediaResult));
} }
if (result.text.isEmpty()) { if (result.empty()) {
result = std::move(logEntryOriginalResult); result = std::move(logEntryOriginalResult);
} else if (!logEntryOriginalResult.text.isEmpty()) { } else if (!logEntryOriginalResult.empty()) {
result.text += qstr("\n\n"); result.append(qstr("\n\n")).append(std::move(logEntryOriginalResult));
TextUtilities::Append(result, std::move(logEntryOriginalResult));
} }
return WrapAsItem(item, std::move(result)); return WrapAsItem(item, std::move(result));
} }
TextWithEntities HistoryGroupText(not_null<const Data::Group*> group) { TextForMimeData HistoryGroupText(not_null<const Data::Group*> group) {
Expects(!group->items.empty()); Expects(!group->items.empty());
auto caption = [&] { auto caption = [&] {
const auto first = begin(group->items); auto &&nonempty = ranges::view::all(
const auto result = (*first)->clipboardText(); group->items
if (result.text.isEmpty()) { ) | ranges::view::filter([](not_null<HistoryItem*> item) {
return result; return !item->clipboardText().empty();
}) | ranges::view::take(2);
auto first = nonempty.begin();
auto end = nonempty.end();
if (first == end) {
return TextForMimeData();
} }
for (auto i = first + 1; i != end(group->items); ++i) { auto result = (*first)->clipboardText();
if (!(*i)->clipboardText().text.isEmpty()) { return (++first == end) ? result : TextForMimeData();
return TextWithEntities();
}
}
return result;
}(); }();
return WrapAsItem(group->items.back(), Data::WithCaptionClipboardText( return WrapAsItem(group->items.back(), Data::WithCaptionClipboardText(
lang(lng_in_dlg_album), lang(lng_in_dlg_album),

View File

@ -13,5 +13,5 @@ namespace Data {
struct Group; struct Group;
} // namespace Data } // namespace Data
TextWithEntities HistoryItemText(not_null<HistoryItem*> item); TextForMimeData HistoryItemText(not_null<HistoryItem*> item);
TextWithEntities HistoryGroupText(not_null<const Data::Group*> group); TextForMimeData HistoryGroupText(not_null<const Data::Group*> group);

View File

@ -1009,9 +1009,9 @@ Storage::SharedMediaTypesMask HistoryMessage::sharedMediaTypes() const {
void HistoryMessage::setText(const TextWithEntities &textWithEntities) { void HistoryMessage::setText(const TextWithEntities &textWithEntities) {
for_const (auto &entity, textWithEntities.entities) { for_const (auto &entity, textWithEntities.entities) {
auto type = entity.type(); auto type = entity.type();
if (type == EntityInTextUrl if (type == EntityType::Url
|| type == EntityInTextCustomUrl || type == EntityType::CustomUrl
|| type == EntityInTextEmail) { || type == EntityType::Email) {
_flags |= MTPDmessage_ClientFlag::f_has_text_links; _flags |= MTPDmessage_ClientFlag::f_has_text_links;
break; break;
} }
@ -1096,11 +1096,11 @@ TextWithEntities HistoryMessage::originalText() const {
return _text.toTextWithEntities(); return _text.toTextWithEntities();
} }
TextWithEntities HistoryMessage::clipboardText() const { TextForMimeData HistoryMessage::clipboardText() const {
if (emptyText()) { if (emptyText()) {
return { QString(), EntitiesInText() }; return TextForMimeData();
} }
return _text.toTextWithEntities(AllTextSelection, ExpandLinksAll); return _text.toTextForMimeData();
} }
bool HistoryMessage::textHasLinks() const { bool HistoryMessage::textHasLinks() const {

View File

@ -123,7 +123,7 @@ public:
void setText(const TextWithEntities &textWithEntities) override; void setText(const TextWithEntities &textWithEntities) override;
TextWithEntities originalText() const override; TextWithEntities originalText() const override;
TextWithEntities clipboardText() const override; TextForMimeData clipboardText() const override;
bool textHasLinks() const override; bool textHasLinks() const override;
int viewsCount() const override; int viewsCount() const override;

View File

@ -49,9 +49,9 @@ public:
[[nodiscard]] not_null<History*> history() const; [[nodiscard]] not_null<History*> history() const;
[[nodiscard]] virtual TextWithEntities selectedText( [[nodiscard]] virtual TextForMimeData selectedText(
TextSelection selection) const { TextSelection selection) const {
return TextWithEntities(); return TextForMimeData();
} }
[[nodiscard]] virtual bool isDisplayed() const; [[nodiscard]] virtual bool isDisplayed() const;

View File

@ -651,12 +651,12 @@ bool HistoryDocument::hasTextForCopy() const {
return Has<HistoryDocumentCaptioned>(); return Has<HistoryDocumentCaptioned>();
} }
TextWithEntities HistoryDocument::selectedText(TextSelection selection) const { TextForMimeData HistoryDocument::selectedText(TextSelection selection) const {
if (const auto captioned = Get<HistoryDocumentCaptioned>()) { if (const auto captioned = Get<HistoryDocumentCaptioned>()) {
const auto &caption = captioned->_caption; const auto &caption = captioned->_caption;
return caption.toTextWithEntities(selection, ExpandLinksAll); return captioned->_caption.toTextForMimeData(selection);
} }
return TextWithEntities(); return TextForMimeData();
} }
bool HistoryDocument::uploading() const { bool HistoryDocument::uploading() const {

View File

@ -30,7 +30,7 @@ public:
uint16 fullSelectionLength() const override; uint16 fullSelectionLength() const override;
bool hasTextForCopy() const override; bool hasTextForCopy() const override;
TextWithEntities selectedText(TextSelection selection) const override; TextForMimeData selectedText(TextSelection selection) const override;
bool uploading() const override; bool uploading() const override;

View File

@ -367,22 +367,16 @@ void HistoryGame::clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pres
} }
} }
TextWithEntities HistoryGame::selectedText(TextSelection selection) const { TextForMimeData HistoryGame::selectedText(TextSelection selection) const {
auto titleResult = _title.toTextWithEntities( auto titleResult = _title.toTextForMimeData(selection);
selection, auto descriptionResult = _description.toTextForMimeData(
ExpandLinksAll); toDescriptionSelection(selection));
auto descriptionResult = _description.toTextWithEntities( if (titleResult.empty()) {
toDescriptionSelection(selection),
ExpandLinksAll);
if (titleResult.text.isEmpty()) {
return descriptionResult; return descriptionResult;
} else if (descriptionResult.text.isEmpty()) { } else if (descriptionResult.empty()) {
return titleResult; return titleResult;
} }
return titleResult.append('\n').append(std::move(descriptionResult));
titleResult.text += '\n';
TextUtilities::Append(titleResult, std::move(descriptionResult));
return titleResult;
} }
void HistoryGame::playAnimation(bool autoplay) { void HistoryGame::playAnimation(bool autoplay) {

View File

@ -40,7 +40,7 @@ public:
return _attach && _attach->dragItemByHandler(p); return _attach && _attach->dragItemByHandler(p);
} }
TextWithEntities selectedText(TextSelection selection) const override; TextForMimeData selectedText(TextSelection selection) const override;
void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override; void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override;
void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override; void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override;

View File

@ -680,8 +680,8 @@ TextState HistoryGif::textState(QPoint point, StateRequest request) const {
return result; return result;
} }
TextWithEntities HistoryGif::selectedText(TextSelection selection) const { TextForMimeData HistoryGif::selectedText(TextSelection selection) const {
return _caption.toTextWithEntities(selection, ExpandLinksAll); return _caption.toTextForMimeData(selection);
} }
bool HistoryGif::uploading() const { bool HistoryGif::uploading() const {

View File

@ -46,7 +46,7 @@ public:
return !_caption.isEmpty(); return !_caption.isEmpty();
} }
TextWithEntities selectedText(TextSelection selection) const override; TextForMimeData selectedText(TextSelection selection) const override;
bool uploading() const override; bool uploading() const override;

View File

@ -304,9 +304,9 @@ TextSelection HistoryGroupedMedia::adjustSelection(
return _caption.adjustSelection(selection, type); return _caption.adjustSelection(selection, type);
} }
TextWithEntities HistoryGroupedMedia::selectedText( TextForMimeData HistoryGroupedMedia::selectedText(
TextSelection selection) const { TextSelection selection) const {
return _caption.toTextWithEntities(selection, ExpandLinksAll); return _caption.toTextForMimeData(selection);
} }
void HistoryGroupedMedia::clickHandlerActiveChanged( void HistoryGroupedMedia::clickHandlerActiveChanged(

View File

@ -55,7 +55,7 @@ public:
PhotoData *getPhoto() const override; PhotoData *getPhoto() const override;
DocumentData *getDocument() const override; DocumentData *getDocument() const override;
TextWithEntities selectedText(TextSelection selection) const override; TextForMimeData selectedText(TextSelection selection) const override;
void clickHandlerActiveChanged( void clickHandlerActiveChanged(
const ClickHandlerPtr &p, const ClickHandlerPtr &p,

View File

@ -57,7 +57,10 @@ void HistoryInvoice::fillFromData(not_null<Data::Invoice*> invoice) {
FillAmountAndCurrency(invoice->amount, invoice->currency), FillAmountAndCurrency(invoice->amount, invoice->currency),
EntitiesInText() EntitiesInText()
}; };
statusText.entities.push_back(EntityInText(EntityInTextBold, 0, statusText.text.size())); statusText.entities.push_back({
EntityType::Bold,
0,
statusText.text.size() });
statusText.text += ' ' + labelText().toUpper(); statusText.text += ' ' + labelText().toUpper();
_status.setMarkedText( _status.setMarkedText(
st::defaultTextStyle, st::defaultTextStyle,
@ -358,22 +361,16 @@ void HistoryInvoice::clickHandlerPressedChanged(const ClickHandlerPtr &p, bool p
} }
} }
TextWithEntities HistoryInvoice::selectedText(TextSelection selection) const { TextForMimeData HistoryInvoice::selectedText(TextSelection selection) const {
auto titleResult = _title.toTextWithEntities( auto titleResult = _title.toTextForMimeData(selection);
selection, auto descriptionResult = _description.toTextForMimeData(
ExpandLinksAll); toDescriptionSelection(selection));
auto descriptionResult = _description.toTextWithEntities( if (titleResult.empty()) {
toDescriptionSelection(selection),
ExpandLinksAll);
if (titleResult.text.isEmpty()) {
return descriptionResult; return descriptionResult;
} else if (descriptionResult.text.isEmpty()) { } else if (descriptionResult.empty()) {
return titleResult; return titleResult;
} }
return titleResult.append('\n').append(std::move(descriptionResult));
titleResult.text += '\n';
TextUtilities::Append(titleResult, std::move(descriptionResult));
return titleResult;
} }
QMargins HistoryInvoice::inBubblePadding() const { QMargins HistoryInvoice::inBubblePadding() const {

View File

@ -52,7 +52,7 @@ public:
return _attach && _attach->dragItemByHandler(p); return _attach && _attach->dragItemByHandler(p);
} }
TextWithEntities selectedText(TextSelection selection) const override; TextForMimeData selectedText(TextSelection selection) const override;
void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override; void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override;
void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override; void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override;

View File

@ -278,17 +278,16 @@ TextSelection HistoryLocation::adjustSelection(TextSelection selection, TextSele
return { titleSelection.from, fromDescriptionSelection(descriptionSelection).to }; return { titleSelection.from, fromDescriptionSelection(descriptionSelection).to };
} }
TextWithEntities HistoryLocation::selectedText(TextSelection selection) const { TextForMimeData HistoryLocation::selectedText(TextSelection selection) const {
auto titleResult = _title.toTextWithEntities(selection); auto titleResult = _title.toTextForMimeData(selection);
auto descriptionResult = _description.toTextWithEntities(toDescriptionSelection(selection)); auto descriptionResult = _description.toTextForMimeData(
if (titleResult.text.isEmpty()) { toDescriptionSelection(selection));
if (titleResult.empty()) {
return descriptionResult; return descriptionResult;
} else if (descriptionResult.text.isEmpty()) { } else if (descriptionResult.empty()) {
return titleResult; return titleResult;
} }
titleResult.text += '\n'; return titleResult.append('\n').append(std::move(descriptionResult));
TextUtilities::Append(titleResult, std::move(descriptionResult));
return titleResult;
} }
bool HistoryLocation::needsBubble() const { bool HistoryLocation::needsBubble() const {

View File

@ -40,7 +40,7 @@ public:
return p == _link; return p == _link;
} }
TextWithEntities selectedText(TextSelection selection) const override; TextForMimeData selectedText(TextSelection selection) const override;
bool needsBubble() const override; bool needsBubble() const override;
bool customInfoLayout() const override { bool customInfoLayout() const override {

View File

@ -535,8 +535,8 @@ void HistoryPhoto::validateGroupedCache(
*cache = image->pixNoCache(_realParent->fullId(), pixWidth, pixHeight, options, width, height); *cache = image->pixNoCache(_realParent->fullId(), pixWidth, pixHeight, options, width, height);
} }
TextWithEntities HistoryPhoto::selectedText(TextSelection selection) const { TextForMimeData HistoryPhoto::selectedText(TextSelection selection) const {
return _caption.toTextWithEntities(selection, ExpandLinksAll); return _caption.toTextForMimeData(selection);
} }
bool HistoryPhoto::needsBubble() const { bool HistoryPhoto::needsBubble() const {

View File

@ -36,7 +36,7 @@ public:
return !_caption.isEmpty(); return !_caption.isEmpty();
} }
TextWithEntities selectedText(TextSelection selection) const override; TextForMimeData selectedText(TextSelection selection) const override;
PhotoData *getPhoto() const override { PhotoData *getPhoto() const override {
return _data; return _data;

View File

@ -576,8 +576,8 @@ void HistoryVideo::setStatusSize(int newSize) const {
HistoryFileMedia::setStatusSize(newSize, _data->size, _data->getDuration(), 0); HistoryFileMedia::setStatusSize(newSize, _data->size, _data->getDuration(), 0);
} }
TextWithEntities HistoryVideo::selectedText(TextSelection selection) const { TextForMimeData HistoryVideo::selectedText(TextSelection selection) const {
return _caption.toTextWithEntities(selection, ExpandLinksAll); return _caption.toTextForMimeData(selection);
} }
bool HistoryVideo::needsBubble() const { bool HistoryVideo::needsBubble() const {

View File

@ -31,7 +31,7 @@ public:
return !_caption.isEmpty(); return !_caption.isEmpty();
} }
TextWithEntities selectedText(TextSelection selection) const override; TextForMimeData selectedText(TextSelection selection) const override;
DocumentData *getDocument() const override { DocumentData *getDocument() const override {
return _data; return _data;

View File

@ -118,7 +118,7 @@ QSize HistoryWebPage::countOptimalSize() {
const auto simplified = simplify(_data->url); const auto simplified = simplify(_data->url);
const auto full = _parent->data()->originalText(); const auto full = _parent->data()->originalText();
for (const auto &entity : full.entities) { for (const auto &entity : full.entities) {
if (entity.type() != EntityInTextUrl) { if (entity.type() != EntityType::Url) {
continue; continue;
} }
const auto link = full.text.mid( const auto link = full.text.mid(
@ -685,22 +685,17 @@ bool HistoryWebPage::isDisplayed() const {
&& !item->Has<HistoryMessageLogEntryOriginal>(); && !item->Has<HistoryMessageLogEntryOriginal>();
} }
TextWithEntities HistoryWebPage::selectedText(TextSelection selection) const { TextForMimeData HistoryWebPage::selectedText(TextSelection selection) const {
auto titleResult = _title.toTextWithEntities( auto titleResult = _title.toTextForMimeData(selection);
selection, auto descriptionResult = _description.toTextForMimeData(
ExpandLinksAll); toDescriptionSelection(selection));
auto descriptionResult = _description.toTextWithEntities( if (titleResult.empty()) {
toDescriptionSelection(selection),
ExpandLinksAll);
if (titleResult.text.isEmpty()) {
return descriptionResult; return descriptionResult;
} else if (descriptionResult.text.isEmpty()) { } else if (descriptionResult.empty()) {
return titleResult; return titleResult;
} }
titleResult.text += '\n'; return titleResult.append('\n').append(std::move(descriptionResult));
TextUtilities::Append(titleResult, std::move(descriptionResult));
return titleResult;
} }
QMargins HistoryWebPage::inBubblePadding() const { QMargins HistoryWebPage::inBubblePadding() const {

View File

@ -45,7 +45,7 @@ public:
return _attach && _attach->dragItemByHandler(p); return _attach && _attach->dragItemByHandler(p);
} }
TextWithEntities selectedText(TextSelection selection) const override; TextForMimeData selectedText(TextSelection selection) const override;
void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override; void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override;
void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override; void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override;

View File

@ -454,14 +454,14 @@ base::unique_qptr<Ui::PopupMenu> FillContextMenu(
const auto isVoiceLink = document ? document->isVoiceMessage() : false; const auto isVoiceLink = document ? document->isVoiceMessage() : false;
const auto isAudioLink = document ? document->isAudioFile() : false; const auto isAudioLink = document ? document->isAudioFile() : false;
const auto hasSelection = !request.selectedItems.empty() const auto hasSelection = !request.selectedItems.empty()
|| !request.selectedText.text.isEmpty(); || !request.selectedText.empty();
if (request.overSelection) { if (request.overSelection) {
const auto text = lang(request.selectedItems.empty() const auto text = lang(request.selectedItems.empty()
? lng_context_copy_selected ? lng_context_copy_selected
: lng_context_copy_selected_items); : lng_context_copy_selected_items);
result->addAction(text, [=] { result->addAction(text, [=] {
SetClipboardWithEntities(list->getSelectedText()); SetClipboardText(list->getSelectedText());
}); });
} }
@ -493,11 +493,11 @@ base::unique_qptr<Ui::PopupMenu> FillContextMenu(
if (const auto item = App::histItemById(itemId)) { if (const auto item = App::histItemById(itemId)) {
if (asGroup) { if (asGroup) {
if (const auto group = Auth().data().groups().find(item)) { if (const auto group = Auth().data().groups().find(item)) {
SetClipboardWithEntities(HistoryGroupText(group)); SetClipboardText(HistoryGroupText(group));
return; return;
} }
} }
SetClipboardWithEntities(HistoryItemText(item)); SetClipboardText(HistoryItemText(item));
} }
}); });
} }

View File

@ -26,7 +26,7 @@ struct ContextMenuRequest {
Element *view = nullptr; Element *view = nullptr;
HistoryItem *item = nullptr; HistoryItem *item = nullptr;
SelectedItems selectedItems; SelectedItems selectedItems;
TextWithEntities selectedText; TextForMimeData selectedText;
bool overSelection = false; bool overSelection = false;
PointState pointState = PointState(); PointState pointState = PointState();
}; };

View File

@ -195,7 +195,7 @@ public:
int bottom, int bottom,
QPoint point, QPoint point,
InfoDisplayType type) const; InfoDisplayType type) const;
virtual TextWithEntities selectedText( virtual TextForMimeData selectedText(
TextSelection selection) const = 0; TextSelection selection) const = 0;
[[nodiscard]] virtual TextSelection adjustSelection( [[nodiscard]] virtual TextSelection adjustSelection(
TextSelection selection, TextSelection selection,

View File

@ -952,7 +952,7 @@ void ListWidget::clearTextSelection() {
} }
_selectedTextItem = nullptr; _selectedTextItem = nullptr;
_selectedTextRange = TextSelection(); _selectedTextRange = TextSelection();
_selectedText = TextWithEntities(); _selectedText = TextForMimeData();
} }
} }
@ -968,9 +968,9 @@ void ListWidget::setTextSelection(
_selectedTextRange = selection; _selectedTextRange = selection;
_selectedText = (selection.from != selection.to) _selectedText = (selection.from != selection.to)
? view->selectedText(selection) ? view->selectedText(selection)
: TextWithEntities(); : TextForMimeData();
repaintItem(view); repaintItem(view);
if (!_wasSelectedText && !_selectedText.text.isEmpty()) { if (!_wasSelectedText && !_selectedText.empty()) {
_wasSelectedText = true; _wasSelectedText = true;
setFocus(); setFocus();
} }
@ -1394,7 +1394,7 @@ void ListWidget::applyDragSelection(SelectedMap &applyTo) const {
} }
} }
TextWithEntities ListWidget::getSelectedText() const { TextForMimeData ListWidget::getSelectedText() const {
auto selected = _selected; auto selected = _selected;
if (_mouseAction == MouseAction::Selecting && !_dragSelected.empty()) { if (_mouseAction == MouseAction::Selecting && !_dragSelected.empty()) {
@ -1413,20 +1413,20 @@ TextWithEntities ListWidget::getSelectedText() const {
auto fullSize = 0; auto fullSize = 0;
auto texts = std::vector<std::pair< auto texts = std::vector<std::pair<
not_null<HistoryItem*>, not_null<HistoryItem*>,
TextWithEntities>>(); TextForMimeData>>();
texts.reserve(selected.size()); texts.reserve(selected.size());
const auto wrapItem = [&]( const auto wrapItem = [&](
not_null<HistoryItem*> item, not_null<HistoryItem*> item,
TextWithEntities &&unwrapped) { TextForMimeData &&unwrapped) {
auto time = ItemDateTime(item).toString(timeFormat); auto time = ItemDateTime(item).toString(timeFormat);
auto part = TextWithEntities(); auto part = TextForMimeData();
auto size = item->author()->name.size() auto size = item->author()->name.size()
+ time.size() + time.size()
+ unwrapped.text.size(); + unwrapped.expanded.size();
part.text.reserve(size); part.reserve(size);
part.text.append(item->author()->name).append(time); part.append(item->author()->name).append(time);
TextUtilities::Append(part, std::move(unwrapped)); part.append(std::move(unwrapped));
texts.emplace_back(std::move(item), std::move(part)); texts.emplace_back(std::move(item), std::move(part));
fullSize += size; fullSize += size;
}; };
@ -1457,18 +1457,18 @@ TextWithEntities ListWidget::getSelectedText() const {
} }
} }
ranges::sort(texts, [&]( ranges::sort(texts, [&](
const std::pair<not_null<HistoryItem*>, TextWithEntities> &a, const std::pair<not_null<HistoryItem*>, TextForMimeData> &a,
const std::pair<not_null<HistoryItem*>, TextWithEntities> &b) { const std::pair<not_null<HistoryItem*>, TextForMimeData> &b) {
return _delegate->listIsLessInOrder(a.first, b.first); return _delegate->listIsLessInOrder(a.first, b.first);
}); });
auto result = TextWithEntities(); auto result = TextForMimeData();
auto sep = qsl("\n\n"); auto sep = qstr("\n\n");
result.text.reserve(fullSize + (texts.size() - 1) * sep.size()); result.reserve(fullSize + (texts.size() - 1) * sep.size());
for (auto i = begin(texts), e = end(texts); i != e;) { for (auto i = begin(texts), e = end(texts); i != e;) {
TextUtilities::Append(result, std::move(i->second)); result.append(std::move(i->second));
if (++i != e) { if (++i != e) {
result.text.append(sep); result.append(sep);
} }
} }
return result; return result;
@ -1523,11 +1523,11 @@ void ListWidget::keyPressEvent(QKeyEvent *e) {
} }
} else if (e == QKeySequence::Copy } else if (e == QKeySequence::Copy
&& (hasSelectedText() || hasSelectedItems())) { && (hasSelectedText() || hasSelectedItems())) {
SetClipboardWithEntities(getSelectedText()); SetClipboardText(getSelectedText());
#ifdef Q_OS_MAC #ifdef Q_OS_MAC
} else if (e->key() == Qt::Key_E } else if (e->key() == Qt::Key_E
&& e->modifiers().testFlag(Qt::ControlModifier)) { && e->modifiers().testFlag(Qt::ControlModifier)) {
SetClipboardWithEntities(getSelectedText(), QClipboard::FindBuffer); SetClipboardText(getSelectedText(), QClipboard::FindBuffer);
#endif // Q_OS_MAC #endif // Q_OS_MAC
} else if (e == QKeySequence::Delete) { } else if (e == QKeySequence::Delete) {
_delegate->listDeleteRequest(); _delegate->listDeleteRequest();
@ -2268,22 +2268,19 @@ std::unique_ptr<QMimeData> ListWidget::prepareDrag() {
_pressItemExact ? _pressItemExact : pressedItem, _pressItemExact ? _pressItemExact : pressedItem,
_pressState); _pressState);
QList<QUrl> urls; auto urls = QList<QUrl>();
auto text = [&] { const auto selectedText = [&] {
if (uponSelected) { if (uponSelected) {
return getSelectedText(); return getSelectedText();
} else if (pressedHandler) { } else if (pressedHandler) {
return TextWithEntities{ //if (!sel.isEmpty() && sel.at(0) != '/' && sel.at(0) != '@' && sel.at(0) != '#') {
pressedHandler->dragText(), // urls.push_back(QUrl::fromEncoded(sel.toUtf8())); // Google Chrome crashes in Mac OS X O_o
EntitiesInText() //}
}; return TextForMimeData::Simple(pressedHandler->dragText());
} }
return TextWithEntities(); return TextForMimeData();
//if (!sel.isEmpty() && sel.at(0) != '/' && sel.at(0) != '@' && sel.at(0) != '#') {
// urls.push_back(QUrl::fromEncoded(sel.toUtf8())); // Google Chrome crashes in Mac OS X O_o
//}
}(); }();
if (auto mimeData = MimeDataFromTextWithEntities(text)) { if (auto mimeData = MimeDataFromText(selectedText)) {
clearDragSelection(); clearDragSelection();
// _widget->noSelectingScroll(); #TODO scroll // _widget->noSelectingScroll(); #TODO scroll

View File

@ -153,7 +153,7 @@ public:
bool isBelowPosition(Data::MessagePosition position) const; bool isBelowPosition(Data::MessagePosition position) const;
void highlightMessage(FullMsgId itemId); void highlightMessage(FullMsgId itemId);
TextWithEntities getSelectedText() const; TextForMimeData getSelectedText() const;
MessageIdsList getSelectedItems() const; MessageIdsList getSelectedItems() const;
void cancelSelection(); void cancelSelection();
void selectItem(not_null<HistoryItem*> item); void selectItem(not_null<HistoryItem*> item);
@ -466,7 +466,7 @@ private:
bool _selectEnabled = false; bool _selectEnabled = false;
HistoryItem *_selectedTextItem = nullptr; HistoryItem *_selectedTextItem = nullptr;
TextSelection _selectedTextRange; TextSelection _selectedTextRange;
TextWithEntities _selectedText; TextForMimeData _selectedText;
SelectedMap _selected; SelectedMap _selected;
base::flat_set<FullMsgId> _dragSelected; base::flat_set<FullMsgId> _dragSelected;
DragSelectAction _dragSelectAction = DragSelectAction::None; DragSelectAction _dragSelectAction = DragSelectAction::None;

View File

@ -1054,19 +1054,17 @@ void Message::updatePressed(QPoint point) {
} }
} }
TextWithEntities Message::selectedText(TextSelection selection) const { TextForMimeData Message::selectedText(TextSelection selection) const {
const auto item = message(); const auto item = message();
const auto media = this->media(); const auto media = this->media();
TextWithEntities logEntryOriginalResult; auto logEntryOriginalResult = TextForMimeData();
auto textResult = item->_text.toTextWithEntities( auto textResult = item->_text.toTextForMimeData(selection);
selection,
ExpandLinksAll);
auto skipped = skipTextSelection(selection); auto skipped = skipTextSelection(selection);
auto mediaDisplayed = (media && media->isDisplayed()); auto mediaDisplayed = (media && media->isDisplayed());
auto mediaResult = (mediaDisplayed || isHiddenByGroup()) auto mediaResult = (mediaDisplayed || isHiddenByGroup())
? media->selectedText(skipped) ? media->selectedText(skipped)
: TextWithEntities(); : TextForMimeData();
if (auto entry = logEntryOriginal()) { if (auto entry = logEntryOriginal()) {
const auto originalSelection = mediaDisplayed const auto originalSelection = mediaDisplayed
? media->skipSelection(skipped) ? media->skipSelection(skipped)
@ -1074,17 +1072,15 @@ TextWithEntities Message::selectedText(TextSelection selection) const {
logEntryOriginalResult = entry->selectedText(originalSelection); logEntryOriginalResult = entry->selectedText(originalSelection);
} }
auto result = textResult; auto result = textResult;
if (result.text.isEmpty()) { if (result.empty()) {
result = std::move(mediaResult); result = std::move(mediaResult);
} else if (!mediaResult.text.isEmpty()) { } else if (!mediaResult.empty()) {
result.text += qstr("\n\n"); result.append(qstr("\n\n")).append(std::move(mediaResult));
TextUtilities::Append(result, std::move(mediaResult));
} }
if (result.text.isEmpty()) { if (result.empty()) {
result = std::move(logEntryOriginalResult); result = std::move(logEntryOriginalResult);
} else if (!logEntryOriginalResult.text.isEmpty()) { } else if (!logEntryOriginalResult.empty()) {
result.text += qstr("\n\n"); result.append(qstr("\n\n")).append(std::move(logEntryOriginalResult));
TextUtilities::Append(result, std::move(logEntryOriginalResult));
} }
return result; return result;
} }

View File

@ -56,7 +56,7 @@ public:
int bottom, int bottom,
QPoint point, QPoint point,
InfoDisplayType type) const override; InfoDisplayType type) const override;
TextWithEntities selectedText(TextSelection selection) const override; TextForMimeData selectedText(TextSelection selection) const override;
TextSelection adjustSelection( TextSelection adjustSelection(
TextSelection selection, TextSelection selection,
TextSelectType type) const override; TextSelectType type) const override;

View File

@ -530,8 +530,8 @@ TextState Service::textState(QPoint point, StateRequest request) const {
void Service::updatePressed(QPoint point) { void Service::updatePressed(QPoint point) {
} }
TextWithEntities Service::selectedText(TextSelection selection) const { TextForMimeData Service::selectedText(TextSelection selection) const {
return message()->_text.toTextWithEntities(selection); return message()->_text.toTextForMimeData(selection);
} }
TextSelection Service::adjustSelection( TextSelection Service::adjustSelection(

View File

@ -32,7 +32,7 @@ public:
QPoint point, QPoint point,
StateRequest request) const override; StateRequest request) const override;
void updatePressed(QPoint point) override; void updatePressed(QPoint point) override;
TextWithEntities selectedText(TextSelection selection) const override; TextForMimeData selectedText(TextSelection selection) const override;
TextSelection adjustSelection( TextSelection adjustSelection(
TextSelection selection, TextSelection selection,
TextSelectType type) const override; TextSelectType type) const override;

View File

@ -1923,7 +1923,7 @@ void ListWidget::performDrag() {
// urls.push_back(QUrl::fromEncoded(sel.toUtf8())); // Google Chrome crashes in Mac OS X O_o // urls.push_back(QUrl::fromEncoded(sel.toUtf8())); // Google Chrome crashes in Mac OS X O_o
//} //}
} }
//if (auto mimeData = MimeDataFromTextWithEntities(sel)) { //if (auto mimeData = MimeDataFromText(sel)) {
// clearDragSelection(); // clearDragSelection();
// _widget->noSelectingScroll(); // _widget->noSelectingScroll();

View File

@ -276,11 +276,11 @@ object_ptr<Ui::RpWidget> DetailsFiller::setupInfo() {
if (result.text.startsWith(remove)) { if (result.text.startsWith(remove)) {
result.text.remove(0, remove.size()); result.text.remove(0, remove.size());
} }
result.entities.push_back(EntityInText( result.entities.push_back({
EntityInTextCustomUrl, EntityType::CustomUrl,
0, 0,
result.text.size(), result.text.size(),
link)); link });
} }
return result; return result;
}); });

View File

@ -73,21 +73,21 @@ void ConfirmSwitchBox::prepare() {
setTitle(langFactory(lng_language_switch_title)); setTitle(langFactory(lng_language_switch_title));
auto link = TextWithEntities{ lang(lng_language_switch_link) }; auto link = TextWithEntities{ lang(lng_language_switch_link) };
link.entities.push_back(EntityInText( link.entities.push_back({
EntityInTextCustomUrl, EntityType::CustomUrl,
0, 0,
link.text.size(), link.text.size(),
QString("internal:go_to_translations"))); QString("internal:go_to_translations") });
auto name = TextWithEntities{ _name }; auto name = TextWithEntities{ _name };
name.entities.push_back(EntityInText( name.entities.push_back({
EntityInTextBold, EntityType::Bold,
0, 0,
name.text.size())); name.text.size() });
auto percent = TextWithEntities{ QString::number(_percent) }; auto percent = TextWithEntities{ QString::number(_percent) };
percent.entities.push_back(EntityInText( percent.entities.push_back({
EntityInTextBold, EntityType::Bold,
0, 0,
percent.text.size())); percent.text.size() });
const auto text = (_official const auto text = (_official
? lng_language_switch_about_official__generic<TextWithEntities> ? lng_language_switch_about_official__generic<TextWithEntities>
: lng_language_switch_about_unofficial__generic<TextWithEntities>)( : lng_language_switch_about_unofficial__generic<TextWithEntities>)(
@ -134,11 +134,11 @@ void NotReadyBox::prepare() {
setTitle(langFactory(lng_language_not_ready_title)); setTitle(langFactory(lng_language_not_ready_title));
auto link = TextWithEntities{ lang(lng_language_not_ready_link) }; auto link = TextWithEntities{ lang(lng_language_not_ready_link) };
link.entities.push_back(EntityInText( link.entities.push_back({
EntityInTextCustomUrl, EntityType::CustomUrl,
0, 0,
link.text.size(), link.text.size(),
QString("internal:go_to_translations"))); QString("internal:go_to_translations") });
auto name = TextWithEntities{ _name }; auto name = TextWithEntities{ _name };
const auto text = lng_language_not_ready_about__generic( const auto text = lng_language_not_ready_about__generic(
lt_lang_name, lt_lang_name,

View File

@ -530,7 +530,7 @@ void Widget::handleSongChange() {
textWithEntities.text = name + ' ' + date(); textWithEntities.text = name + ' ' + date();
textWithEntities.entities.append(EntityInText( textWithEntities.entities.append(EntityInText(
EntityInTextBold, EntityType::Bold,
0, 0,
name.size(), name.size(),
QString())); QString()));
@ -551,7 +551,7 @@ void Widget::handleSongChange() {
: TextUtilities::Clean(song->title); : TextUtilities::Clean(song->title);
auto dash = QString::fromUtf8(" \xe2\x80\x93 "); auto dash = QString::fromUtf8(" \xe2\x80\x93 ");
textWithEntities.text = song->performer + dash + title; textWithEntities.text = song->performer + dash + title;
textWithEntities.entities.append({ EntityInTextBold, 0, song->performer.size(), QString() }); textWithEntities.entities.append({ EntityType::Bold, 0, song->performer.size(), QString() });
} }
} }
_nameLabel->setMarkedText(textWithEntities); _nameLabel->setMarkedText(textWithEntities);

View File

@ -51,13 +51,13 @@ TextWithEntities ComposeNameWithEntities(DocumentData *document) {
result.text = document->filename().isEmpty() result.text = document->filename().isEmpty()
? qsl("Unknown File") ? qsl("Unknown File")
: document->filename(); : document->filename();
result.entities.push_back({ EntityInTextBold, 0, result.text.size() }); result.entities.push_back({ EntityType::Bold, 0, result.text.size() });
} else if (song->performer.isEmpty()) { } else if (song->performer.isEmpty()) {
result.text = song->title; result.text = song->title;
result.entities.push_back({ EntityInTextBold, 0, result.text.size() }); result.entities.push_back({ EntityType::Bold, 0, result.text.size() });
} else { } else {
result.text = song->performer + QString::fromUtf8(" \xe2\x80\x93 ") + (song->title.isEmpty() ? qsl("Unknown Track") : song->title); result.text = song->performer + QString::fromUtf8(" \xe2\x80\x93 ") + (song->title.isEmpty() ? qsl("Unknown Track") : song->title);
result.entities.push_back({ EntityInTextBold, 0, song->performer.size() }); result.entities.push_back({ EntityType::Bold, 0, song->performer.size() });
} }
return result; return result;
} }
@ -1317,7 +1317,7 @@ Link::Link(
int32 from = 0, till = text.size(), lnk = entities.size(); int32 from = 0, till = text.size(), lnk = entities.size();
for (const auto &entity : entities) { for (const auto &entity : entities) {
auto type = entity.type(); auto type = entity.type();
if (type != EntityInTextUrl && type != EntityInTextCustomUrl && type != EntityInTextEmail) { if (type != EntityType::Url && type != EntityType::CustomUrl && type != EntityType::Email) {
continue; continue;
} }
const auto customUrl = entity.data(); const auto customUrl = entity.data();
@ -1332,7 +1332,7 @@ Link::Link(
--lnk; --lnk;
auto &entity = entities.at(lnk); auto &entity = entities.at(lnk);
auto type = entity.type(); auto type = entity.type();
if (type != EntityInTextUrl && type != EntityInTextCustomUrl && type != EntityInTextEmail) { if (type != EntityType::Url && type != EntityType::CustomUrl && type != EntityType::Email) {
++lnk; ++lnk;
break; break;
} }

View File

@ -129,11 +129,11 @@ void VerifyBox::setupControls(
small); small);
if (resend) { if (resend) {
auto link = TextWithEntities{ lang(lng_cloud_password_resend) }; auto link = TextWithEntities{ lang(lng_cloud_password_resend) };
link.entities.push_back(EntityInText( link.entities.push_back({
EntityInTextCustomUrl, EntityType::CustomUrl,
0, 0,
link.text.size(), link.text.size(),
QString("internal:resend"))); QString("internal:resend") });
const auto label = _content->add( const auto label = _content->add(
object_ptr<Ui::FlatLabel>( object_ptr<Ui::FlatLabel>(
_content, _content,

View File

@ -252,11 +252,11 @@ void SetupRows(
return username; return username;
} }
auto result = TextWithEntities{ add }; auto result = TextWithEntities{ add };
result.entities.push_back(EntityInText( result.entities.push_back({
EntityInTextCustomUrl, EntityType::CustomUrl,
0, 0,
add.size(), add.size(),
"internal:edit_username")); "internal:edit_username" });
return result; return result;
}); });
AddRow( AddRow(

View File

@ -253,40 +253,40 @@ public:
int32 startFlags = 0; int32 startFlags = 0;
QString linkData, linkText; QString linkData, linkText;
auto type = waitingEntity->type(), linkType = EntityInTextInvalid; auto type = waitingEntity->type(), linkType = EntityType::Invalid;
LinkDisplayStatus linkDisplayStatus = LinkDisplayedFull; LinkDisplayStatus linkDisplayStatus = LinkDisplayedFull;
if (type == EntityInTextBold) { if (type == EntityType::Bold) {
startFlags = TextBlockFSemibold; startFlags = TextBlockFSemibold;
} else if (type == EntityInTextItalic) { } else if (type == EntityType::Italic) {
startFlags = TextBlockFItalic; startFlags = TextBlockFItalic;
} else if (type == EntityInTextCode) { } else if (type == EntityType::Code) {
startFlags = TextBlockFCode; startFlags = TextBlockFCode;
} else if (type == EntityInTextPre) { } else if (type == EntityType::Pre) {
startFlags = TextBlockFPre; startFlags = TextBlockFPre;
createBlock(); createBlock();
if (!_t->_blocks.empty() && _t->_blocks.back()->type() != TextBlockTNewline) { if (!_t->_blocks.empty() && _t->_blocks.back()->type() != TextBlockTNewline) {
createNewlineBlock(); createNewlineBlock();
} }
} else if (type == EntityInTextUrl } else if (type == EntityType::Url
|| type == EntityInTextEmail || type == EntityType::Email
|| type == EntityInTextMention || type == EntityType::Mention
|| type == EntityInTextHashtag || type == EntityType::Hashtag
|| type == EntityInTextCashtag || type == EntityType::Cashtag
|| type == EntityInTextBotCommand) { || type == EntityType::BotCommand) {
linkType = type; linkType = type;
linkData = QString(start + waitingEntity->offset(), waitingEntity->length()); linkData = QString(start + waitingEntity->offset(), waitingEntity->length());
if (linkType == EntityInTextUrl) { if (linkType == EntityType::Url) {
computeLinkText(linkData, &linkText, &linkDisplayStatus); computeLinkText(linkData, &linkText, &linkDisplayStatus);
} else { } else {
linkText = linkData; linkText = linkData;
} }
} else if (type == EntityInTextCustomUrl || type == EntityInTextMentionName) { } else if (type == EntityType::CustomUrl || type == EntityType::MentionName) {
linkType = type; linkType = type;
linkData = waitingEntity->data(); linkData = waitingEntity->data();
linkText = QString(start + waitingEntity->offset(), waitingEntity->length()); linkText = QString(start + waitingEntity->offset(), waitingEntity->length());
} }
if (linkType != EntityInTextInvalid) { if (linkType != EntityType::Invalid) {
createBlock(); createBlock();
links.push_back(TextLinkData(linkType, linkText, linkData, linkDisplayStatus)); links.push_back(TextLinkData(linkType, linkText, linkData, linkDisplayStatus));
@ -419,7 +419,7 @@ public:
case TextCommandLinkText: { case TextCommandLinkText: {
createBlock(); createBlock();
int32 len = ptr->unicode(); int32 len = ptr->unicode();
links.push_back(TextLinkData(EntityInTextCustomUrl, QString(), QString(++ptr, len), LinkDisplayedFull)); links.push_back(TextLinkData(EntityType::CustomUrl, QString(), QString(++ptr, len), LinkDisplayedFull));
lnkIndex = 0x8000 + links.size(); lnkIndex = 0x8000 + links.size();
} break; } break;
@ -535,11 +535,11 @@ public:
const QChar s = source.text.size(); const QChar s = source.text.size();
for (; i < l; ++i) { for (; i < l; ++i) {
auto type = preparsed.at(i).type(); auto type = preparsed.at(i).type();
if (((type == EntityInTextMention || type == EntityInTextMentionName) && !parseMentions) || if (((type == EntityType::Mention || type == EntityType::MentionName) && !parseMentions) ||
(type == EntityInTextHashtag && !parseHashtags) || (type == EntityType::Hashtag && !parseHashtags) ||
(type == EntityInTextCashtag && !parseHashtags) || (type == EntityType::Cashtag && !parseHashtags) ||
(type == EntityInTextBotCommand && !parseBotCommands) || (type == EntityType::BotCommand && !parseBotCommands) ||
((type == EntityInTextBold || type == EntityInTextItalic || type == EntityInTextCode || type == EntityInTextPre) && !parseMarkdown)) { ((type == EntityType::Bold || type == EntityType::Italic || type == EntityType::Code || type == EntityType::Pre) && !parseMarkdown)) {
continue; continue;
} }
source.entities.push_back(preparsed.at(i)); source.entities.push_back(preparsed.at(i));
@ -557,14 +557,14 @@ public:
bool isLinkEntity(const EntityInText &entity) const { bool isLinkEntity(const EntityInText &entity) const {
const auto type = entity.type(); const auto type = entity.type();
const auto urls = { const auto urls = {
EntityInTextUrl, EntityType::Url,
EntityInTextCustomUrl, EntityType::CustomUrl,
EntityInTextEmail, EntityType::Email,
EntityInTextHashtag, EntityType::Hashtag,
EntityInTextCashtag, EntityType::Cashtag,
EntityInTextMention, EntityType::Mention,
EntityInTextMentionName, EntityType::MentionName,
EntityInTextBotCommand EntityType::BotCommand
}; };
return ranges::find(urls, type) != std::end(urls); return ranges::find(urls, type) != std::end(urls);
} }
@ -582,7 +582,9 @@ public:
while (waitingEntity != entitiesEnd && isInvalidEntity(*waitingEntity)) { while (waitingEntity != entitiesEnd && isInvalidEntity(*waitingEntity)) {
++waitingEntity; ++waitingEntity;
} }
int firstMonospaceOffset = EntityInText::firstMonospaceOffset(source.entities, end - start); const auto firstMonospaceOffset = EntityInText::FirstMonospaceOffset(
source.entities,
end - start);
ptr = start; ptr = start;
while (ptr != end && chIsTrimmed(*ptr, rich) && ptr != start + firstMonospaceOffset) { while (ptr != end && chIsTrimmed(*ptr, rich) && ptr != start + firstMonospaceOffset) {
@ -599,7 +601,7 @@ public:
sumWidth = 0; sumWidth = 0;
sumFinished = newlineAwaited = false; sumFinished = newlineAwaited = false;
blockStart = 0; blockStart = 0;
emoji = 0; emoji = nullptr;
ch = emojiLookback = 0; ch = emojiLookback = 0;
lastSkipped = false; lastSkipped = false;
@ -625,8 +627,8 @@ public:
removeFlags.clear(); removeFlags.clear();
_t->_links.resize(maxLnkIndex); _t->_links.resize(maxLnkIndex);
for (auto i = _t->_blocks.cbegin(), e = _t->_blocks.cend(); i != e; ++i) { for (const auto &block : _t->_blocks) {
auto b = i->get(); const auto b = block.get();
if (b->lnkIndex() > 0x8000) { if (b->lnkIndex() > 0x8000) {
lnkIndex = maxLnkIndex + (b->lnkIndex() - 0x8000); lnkIndex = maxLnkIndex + (b->lnkIndex() - 0x8000);
if (_t->_links.size() < lnkIndex) { if (_t->_links.size() < lnkIndex) {
@ -634,15 +636,15 @@ public:
auto &link = links[lnkIndex - maxLnkIndex - 1]; auto &link = links[lnkIndex - maxLnkIndex - 1];
auto handler = ClickHandlerPtr(); auto handler = ClickHandlerPtr();
switch (link.type) { switch (link.type) {
case EntityInTextCustomUrl: { case EntityType::CustomUrl: {
if (!link.data.isEmpty()) { if (!link.data.isEmpty()) {
handler = std::make_shared<HiddenUrlClickHandler>(link.data); handler = std::make_shared<HiddenUrlClickHandler>(link.data);
} }
} break; } break;
case EntityInTextEmail: case EntityType::Email:
case EntityInTextUrl: handler = std::make_shared<UrlClickHandler>(link.data, link.displayStatus == LinkDisplayedFull); break; case EntityType::Url: handler = std::make_shared<UrlClickHandler>(link.data, link.displayStatus == LinkDisplayedFull); break;
case EntityInTextBotCommand: handler = std::make_shared<BotCommandClickHandler>(link.data); break; case EntityType::BotCommand: handler = std::make_shared<BotCommandClickHandler>(link.data); break;
case EntityInTextHashtag: case EntityType::Hashtag:
if (options.flags & TextTwitterMentions) { if (options.flags & TextTwitterMentions) {
handler = std::make_shared<UrlClickHandler>(qsl("https://twitter.com/hashtag/") + link.data.mid(1) + qsl("?src=hash"), true); handler = std::make_shared<UrlClickHandler>(qsl("https://twitter.com/hashtag/") + link.data.mid(1) + qsl("?src=hash"), true);
} else if (options.flags & TextInstagramMentions) { } else if (options.flags & TextInstagramMentions) {
@ -651,10 +653,10 @@ public:
handler = std::make_shared<HashtagClickHandler>(link.data); handler = std::make_shared<HashtagClickHandler>(link.data);
} }
break; break;
case EntityInTextCashtag: case EntityType::Cashtag:
handler = std::make_shared<CashtagClickHandler>(link.data); handler = std::make_shared<CashtagClickHandler>(link.data);
break; break;
case EntityInTextMention: case EntityType::Mention:
if (options.flags & TextTwitterMentions) { if (options.flags & TextTwitterMentions) {
handler = std::make_shared<UrlClickHandler>(qsl("https://twitter.com/") + link.data.mid(1), true); handler = std::make_shared<UrlClickHandler>(qsl("https://twitter.com/") + link.data.mid(1), true);
} else if (options.flags & TextInstagramMentions) { } else if (options.flags & TextInstagramMentions) {
@ -663,7 +665,7 @@ public:
handler = std::make_shared<MentionClickHandler>(link.data); handler = std::make_shared<MentionClickHandler>(link.data);
} }
break; break;
case EntityInTextMentionName: { case EntityType::MentionName: {
auto fields = TextUtilities::MentionNameDataToFields(link.data); auto fields = TextUtilities::MentionNameDataToFields(link.data);
if (fields.userId) { if (fields.userId) {
handler = std::make_shared<MentionNameClickHandler>(link.text, fields.userId, fields.accessHash); handler = std::make_shared<MentionNameClickHandler>(link.text, fields.userId, fields.accessHash);
@ -693,13 +695,13 @@ private:
struct TextLinkData { struct TextLinkData {
TextLinkData() = default; TextLinkData() = default;
TextLinkData(EntityInTextType type, const QString &text, const QString &data, LinkDisplayStatus displayStatus) TextLinkData(EntityType type, const QString &text, const QString &data, LinkDisplayStatus displayStatus)
: type(type) : type(type)
, text(text) , text(text)
, data(data) , data(data)
, displayStatus(displayStatus) { , displayStatus(displayStatus) {
} }
EntityInTextType type = EntityInTextInvalid; EntityType type = EntityType::Invalid;
QString text, data; QString text, data;
LinkDisplayStatus displayStatus = LinkDisplayedFull; LinkDisplayStatus displayStatus = LinkDisplayedFull;
}; };
@ -2927,13 +2929,11 @@ void Text::drawElided(Painter &painter, int32 left, int32 top, int32 w, int32 li
} }
Text::StateResult Text::getState(QPoint point, int width, StateRequest request) const { Text::StateResult Text::getState(QPoint point, int width, StateRequest request) const {
TextPainter p(0, this); return TextPainter(nullptr, this).getState(point, width, request);
return p.getState(point, width, request);
} }
Text::StateResult Text::getStateElided(QPoint point, int width, StateRequestElided request) const { Text::StateResult Text::getStateElided(QPoint point, int width, StateRequestElided request) const {
TextPainter p(0, this); return TextPainter(nullptr, this).getStateElided(point, width, request);
return p.getStateElided(point, width, request);
} }
TextSelection Text::adjustSelection(TextSelection selection, TextSelectType selectType) const { TextSelection Text::adjustSelection(TextSelection selection, TextSelectType selectType) const {
@ -3046,65 +3046,102 @@ void Text::enumerateText(TextSelection selection, AppendPartCallback appendPartC
} }
} }
QString Text::toString(TextSelection selection) const {
return toText(selection, false, false).rich.text;
TextWithEntities Text::toTextWithEntities(TextSelection selection, ExpandLinksMode mode) const {
TextWithEntities result;
result.text.reserve(_text.size());
int lnkStart = 0, italicStart = 0, boldStart = 0, codeStart = 0, preStart = 0;
auto flagsChangeCallback = [&](int32 oldFlags, int32 newFlags) {
if ((oldFlags & TextBlockFItalic) && !(newFlags & TextBlockFItalic)) { // write italic
result.entities.push_back(EntityInText(EntityInTextItalic, italicStart, result.text.size() - italicStart));
} else if ((newFlags & TextBlockFItalic) && !(oldFlags & TextBlockFItalic)) {
italicStart = result.text.size();
}
if ((oldFlags & TextBlockFSemibold) && !(newFlags & TextBlockFSemibold)) {
result.entities.push_back(EntityInText(EntityInTextBold, boldStart, result.text.size() - boldStart));
} else if ((newFlags & TextBlockFSemibold) && !(oldFlags & TextBlockFSemibold)) {
boldStart = result.text.size();
}
if ((oldFlags & TextBlockFCode) && !(newFlags & TextBlockFCode)) {
result.entities.push_back(EntityInText(EntityInTextCode, codeStart, result.text.size() - codeStart));
} else if ((newFlags & TextBlockFCode) && !(oldFlags & TextBlockFCode)) {
codeStart = result.text.size();
}
if ((oldFlags & TextBlockFPre) && !(newFlags & TextBlockFPre)) {
result.entities.push_back(EntityInText(EntityInTextPre, preStart, result.text.size() - preStart));
} else if ((newFlags & TextBlockFPre) && !(oldFlags & TextBlockFPre)) {
preStart = result.text.size();
}
};
auto clickHandlerStartCallback = [&] {
lnkStart = result.text.size();
};
auto clickHandlerFinishCallback = [&](const QStringRef &r, const ClickHandlerPtr &handler) {
const auto expanded = handler->getExpandedLinkTextWithEntities(lnkStart, r);
if (mode == ExpandLinksAll
&& !expanded.entities.isEmpty()
&& expanded.entities[0].type() == EntityInTextCustomUrl) {
result.text += r;
result.text += qsl(" (") + expanded.entities[0].data() + ')';
} else if (expanded.text.isEmpty()) {
result.text += r;
} else {
result.text += expanded.text;
}
if (!expanded.entities.isEmpty()) {
result.entities.append(expanded.entities);
}
};
auto appendPartCallback = [&](const QStringRef &r) {
result.text += r;
};
enumerateText(selection, appendPartCallback, clickHandlerStartCallback, clickHandlerFinishCallback, flagsChangeCallback);
return result;
} }
QString Text::toString(TextSelection selection, ExpandLinksMode mode) const { TextWithEntities Text::toTextWithEntities(TextSelection selection) const {
return toTextWithEntities(selection, mode).text; return toText(selection, false, true).rich;
}
TextForMimeData Text::toTextForMimeData(TextSelection selection) const {
return toText(selection, true, true);
}
TextForMimeData Text::toText(
TextSelection selection,
bool composeExpanded,
bool composeEntities) const {
struct MarkdownTagTracker {
TextBlockFlags flag = TextBlockFlags();
EntityType type = EntityType();
int start = 0;
};
auto result = TextForMimeData();
result.rich.text.reserve(_text.size());
if (composeExpanded) {
result.expanded.reserve(_text.size());
}
auto linkStart = 0;
auto markdownTrackers = composeEntities
? std::vector<MarkdownTagTracker>{
{ TextBlockFItalic, EntityType::Italic },
{ TextBlockFSemibold, EntityType::Bold },
{ TextBlockFCode, EntityType::Code },
{ TextBlockFPre, EntityType::Pre }
} : std::vector<MarkdownTagTracker>();
const auto flagsChangeCallback = [&](int32 oldFlags, int32 newFlags) {
if (!composeEntities) {
return;
}
for (auto &tracker : markdownTrackers) {
const auto flag = tracker.flag;
if ((oldFlags & flag) && !(newFlags & flag)) {
result.rich.entities.push_back({
tracker.type,
tracker.start,
result.rich.text.size() - tracker.start });
} else if ((newFlags & flag) && !(oldFlags & flag)) {
tracker.start = result.rich.text.size();
}
}
};
const auto clickHandlerStartCallback = [&] {
linkStart = result.rich.text.size();
};
const auto clickHandlerFinishCallback = [&](
const QStringRef &part,
const ClickHandlerPtr &handler) {
const auto entity = handler->getTextEntity();
const auto plainUrl = (entity.type == EntityType::Url)
|| (entity.type == EntityType::Email);
const auto full = plainUrl
? entity.data.midRef(0, entity.data.size())
: part;
result.rich.text.append(full);
if (!composeExpanded && !composeEntities) {
return;
}
if (composeExpanded) {
result.expanded.append(full);
if (entity.type == EntityType::CustomUrl) {
const auto &url = entity.data;
result.expanded.append(qstr(" (")).append(url).append(')');
}
}
if (composeEntities) {
result.rich.entities.push_back({
entity.type,
linkStart,
full.size(),
plainUrl ? QString() : entity.data });
}
};
const auto appendPartCallback = [&](const QStringRef &part) {
result.rich.text += part;
if (composeExpanded) {
result.expanded += part;
}
};
enumerateText(
selection,
appendPartCallback,
clickHandlerStartCallback,
clickHandlerFinishCallback,
flagsChangeCallback);
return result;
} }
void Text::clear() { void Text::clear() {

View File

@ -165,8 +165,11 @@ public:
return _text.size(); return _text.size();
} }
QString toString(TextSelection selection = AllTextSelection, ExpandLinksMode mode = ExpandLinksShortened) const; QString toString(TextSelection selection = AllTextSelection) const;
TextWithEntities toTextWithEntities(TextSelection selection = AllTextSelection, ExpandLinksMode mode = ExpandLinksShortened) const; TextWithEntities toTextWithEntities(
TextSelection selection = AllTextSelection) const;
TextForMimeData toTextForMimeData(
TextSelection selection = AllTextSelection) const;
bool lastDots(int32 dots, int32 maxdots = 3) { // hack for typing animation bool lastDots(int32 dots, int32 maxdots = 3) { // hack for typing animation
if (_text.size() < maxdots) return false; if (_text.size() < maxdots) return false;
@ -217,6 +220,11 @@ private:
// it is also called from move constructor / assignment operator // it is also called from move constructor / assignment operator
void clearFields(); void clearFields();
TextForMimeData toText(
TextSelection selection,
bool composeExpanded,
bool composeEntities) const;
QFixed _minResizeWidth; QFixed _minResizeWidth;
QFixed _maxWidth = 0; QFixed _maxWidth = 0;
int32 _minHeight = 0; int32 _minHeight = 0;

View File

@ -1321,8 +1321,8 @@ bool CutPart(TextWithEntities &sending, TextWithEntities &left, int32 limit) {
if (s > half) { if (s > half) {
bool inEntity = (currentEntity < entityCount) && (ch > start + left.entities[currentEntity].offset()) && (ch < start + left.entities[currentEntity].offset() + left.entities[currentEntity].length()); bool inEntity = (currentEntity < entityCount) && (ch > start + left.entities[currentEntity].offset()) && (ch < start + left.entities[currentEntity].offset() + left.entities[currentEntity].length());
EntityInTextType entityType = (currentEntity < entityCount) ? left.entities[currentEntity].type() : EntityInTextInvalid; EntityType entityType = (currentEntity < entityCount) ? left.entities[currentEntity].type() : EntityType::Invalid;
bool canBreakEntity = (entityType == EntityInTextPre || entityType == EntityInTextCode); bool canBreakEntity = (entityType == EntityType::Pre || entityType == EntityType::Code);
int32 noEntityLevel = inEntity ? 0 : 1; int32 noEntityLevel = inEntity ? 0 : 1;
auto markGoodAsLevel = [&](int newLevel) { auto markGoodAsLevel = [&](int newLevel) {
@ -1348,9 +1348,9 @@ bool CutPart(TextWithEntities &sending, TextWithEntities &left, int32 limit) {
} }
} else if (ch + 1 < end && chIsNewline(*(ch + 1))) { } else if (ch + 1 < end && chIsNewline(*(ch + 1))) {
markGoodAsLevel(15); markGoodAsLevel(15);
} else if (currentEntity < entityCount && ch + 1 == start + left.entities[currentEntity].offset() && left.entities[currentEntity].type() == EntityInTextPre) { } else if (currentEntity < entityCount && ch + 1 == start + left.entities[currentEntity].offset() && left.entities[currentEntity].type() == EntityType::Pre) {
markGoodAsLevel(14); markGoodAsLevel(14);
} else if (currentEntity > 0 && ch == start + left.entities[currentEntity - 1].offset() + left.entities[currentEntity - 1].length() && left.entities[currentEntity - 1].type() == EntityInTextPre) { } else if (currentEntity > 0 && ch == start + left.entities[currentEntity - 1].offset() + left.entities[currentEntity - 1].length() && left.entities[currentEntity - 1].type() == EntityType::Pre) {
markGoodAsLevel(14); markGoodAsLevel(14);
} else { } else {
markGoodAsLevel(13); markGoodAsLevel(13);
@ -1463,13 +1463,13 @@ EntitiesInText EntitiesFromMTP(const QVector<MTPMessageEntity> &entities) {
result.reserve(entities.size()); result.reserve(entities.size());
for_const (auto &entity, entities) { for_const (auto &entity, entities) {
switch (entity.type()) { switch (entity.type()) {
case mtpc_messageEntityUrl: { auto &d = entity.c_messageEntityUrl(); result.push_back(EntityInText(EntityInTextUrl, d.voffset.v, d.vlength.v)); } break; case mtpc_messageEntityUrl: { auto &d = entity.c_messageEntityUrl(); result.push_back({ EntityType::Url, d.voffset.v, d.vlength.v }); } break;
case mtpc_messageEntityTextUrl: { auto &d = entity.c_messageEntityTextUrl(); result.push_back(EntityInText(EntityInTextCustomUrl, d.voffset.v, d.vlength.v, Clean(qs(d.vurl)))); } break; case mtpc_messageEntityTextUrl: { auto &d = entity.c_messageEntityTextUrl(); result.push_back({ EntityType::CustomUrl, d.voffset.v, d.vlength.v, Clean(qs(d.vurl)) }); } break;
case mtpc_messageEntityEmail: { auto &d = entity.c_messageEntityEmail(); result.push_back(EntityInText(EntityInTextEmail, d.voffset.v, d.vlength.v)); } break; case mtpc_messageEntityEmail: { auto &d = entity.c_messageEntityEmail(); result.push_back({ EntityType::Email, d.voffset.v, d.vlength.v }); } break;
case mtpc_messageEntityHashtag: { auto &d = entity.c_messageEntityHashtag(); result.push_back(EntityInText(EntityInTextHashtag, d.voffset.v, d.vlength.v)); } break; case mtpc_messageEntityHashtag: { auto &d = entity.c_messageEntityHashtag(); result.push_back({ EntityType::Hashtag, d.voffset.v, d.vlength.v }); } break;
case mtpc_messageEntityCashtag: { auto &d = entity.c_messageEntityCashtag(); result.push_back(EntityInText(EntityInTextCashtag, d.voffset.v, d.vlength.v)); } break; case mtpc_messageEntityCashtag: { auto &d = entity.c_messageEntityCashtag(); result.push_back({ EntityType::Cashtag, d.voffset.v, d.vlength.v }); } break;
case mtpc_messageEntityPhone: break; // Skipping phones. case mtpc_messageEntityPhone: break; // Skipping phones.
case mtpc_messageEntityMention: { auto &d = entity.c_messageEntityMention(); result.push_back(EntityInText(EntityInTextMention, d.voffset.v, d.vlength.v)); } break; case mtpc_messageEntityMention: { auto &d = entity.c_messageEntityMention(); result.push_back({ EntityType::Mention, d.voffset.v, d.vlength.v }); } break;
case mtpc_messageEntityMentionName: { case mtpc_messageEntityMentionName: {
auto &d = entity.c_messageEntityMentionName(); auto &d = entity.c_messageEntityMentionName();
auto data = [&d] { auto data = [&d] {
@ -1480,7 +1480,7 @@ EntitiesInText EntitiesFromMTP(const QVector<MTPMessageEntity> &entities) {
} }
return MentionNameDataFromFields(d.vuser_id.v); return MentionNameDataFromFields(d.vuser_id.v);
}; };
result.push_back(EntityInText(EntityInTextMentionName, d.voffset.v, d.vlength.v, data())); result.push_back({ EntityType::MentionName, d.voffset.v, d.vlength.v, data() });
} break; } break;
case mtpc_inputMessageEntityMentionName: { case mtpc_inputMessageEntityMentionName: {
auto &d = entity.c_inputMessageEntityMentionName(); auto &d = entity.c_inputMessageEntityMentionName();
@ -1494,14 +1494,14 @@ EntitiesInText EntitiesFromMTP(const QVector<MTPMessageEntity> &entities) {
return QString(); return QString();
})(); })();
if (!data.isEmpty()) { if (!data.isEmpty()) {
result.push_back(EntityInText(EntityInTextMentionName, d.voffset.v, d.vlength.v, data)); result.push_back({ EntityType::MentionName, d.voffset.v, d.vlength.v, data });
} }
} break; } break;
case mtpc_messageEntityBotCommand: { auto &d = entity.c_messageEntityBotCommand(); result.push_back(EntityInText(EntityInTextBotCommand, d.voffset.v, d.vlength.v)); } break; case mtpc_messageEntityBotCommand: { auto &d = entity.c_messageEntityBotCommand(); result.push_back({ EntityType::BotCommand, d.voffset.v, d.vlength.v }); } break;
case mtpc_messageEntityBold: { auto &d = entity.c_messageEntityBold(); result.push_back(EntityInText(EntityInTextBold, d.voffset.v, d.vlength.v)); } break; case mtpc_messageEntityBold: { auto &d = entity.c_messageEntityBold(); result.push_back({ EntityType::Bold, d.voffset.v, d.vlength.v }); } break;
case mtpc_messageEntityItalic: { auto &d = entity.c_messageEntityItalic(); result.push_back(EntityInText(EntityInTextItalic, d.voffset.v, d.vlength.v)); } break; case mtpc_messageEntityItalic: { auto &d = entity.c_messageEntityItalic(); result.push_back({ EntityType::Italic, d.voffset.v, d.vlength.v }); } break;
case mtpc_messageEntityCode: { auto &d = entity.c_messageEntityCode(); result.push_back(EntityInText(EntityInTextCode, d.voffset.v, d.vlength.v)); } break; case mtpc_messageEntityCode: { auto &d = entity.c_messageEntityCode(); result.push_back({ EntityType::Code, d.voffset.v, d.vlength.v }); } break;
case mtpc_messageEntityPre: { auto &d = entity.c_messageEntityPre(); result.push_back(EntityInText(EntityInTextPre, d.voffset.v, d.vlength.v, Clean(qs(d.vlanguage)))); } break; case mtpc_messageEntityPre: { auto &d = entity.c_messageEntityPre(); result.push_back({ EntityType::Pre, d.voffset.v, d.vlength.v, Clean(qs(d.vlanguage)) }); } break;
} }
} }
} }
@ -1514,25 +1514,25 @@ MTPVector<MTPMessageEntity> EntitiesToMTP(const EntitiesInText &entities, Conver
for_const (auto &entity, entities) { for_const (auto &entity, entities) {
if (entity.length() <= 0) continue; if (entity.length() <= 0) continue;
if (option == ConvertOption::SkipLocal if (option == ConvertOption::SkipLocal
&& entity.type() != EntityInTextBold && entity.type() != EntityType::Bold
&& entity.type() != EntityInTextItalic && entity.type() != EntityType::Italic
&& entity.type() != EntityInTextCode && entity.type() != EntityType::Code
&& entity.type() != EntityInTextPre && entity.type() != EntityType::Pre
&& entity.type() != EntityInTextMentionName && entity.type() != EntityType::MentionName
&& entity.type() != EntityInTextCustomUrl) { && entity.type() != EntityType::CustomUrl) {
continue; continue;
} }
auto offset = MTP_int(entity.offset()); auto offset = MTP_int(entity.offset());
auto length = MTP_int(entity.length()); auto length = MTP_int(entity.length());
switch (entity.type()) { switch (entity.type()) {
case EntityInTextUrl: v.push_back(MTP_messageEntityUrl(offset, length)); break; case EntityType::Url: v.push_back(MTP_messageEntityUrl(offset, length)); break;
case EntityInTextCustomUrl: v.push_back(MTP_messageEntityTextUrl(offset, length, MTP_string(entity.data()))); break; case EntityType::CustomUrl: v.push_back(MTP_messageEntityTextUrl(offset, length, MTP_string(entity.data()))); break;
case EntityInTextEmail: v.push_back(MTP_messageEntityEmail(offset, length)); break; case EntityType::Email: v.push_back(MTP_messageEntityEmail(offset, length)); break;
case EntityInTextHashtag: v.push_back(MTP_messageEntityHashtag(offset, length)); break; case EntityType::Hashtag: v.push_back(MTP_messageEntityHashtag(offset, length)); break;
case EntityInTextCashtag: v.push_back(MTP_messageEntityCashtag(offset, length)); break; case EntityType::Cashtag: v.push_back(MTP_messageEntityCashtag(offset, length)); break;
case EntityInTextMention: v.push_back(MTP_messageEntityMention(offset, length)); break; case EntityType::Mention: v.push_back(MTP_messageEntityMention(offset, length)); break;
case EntityInTextMentionName: { case EntityType::MentionName: {
auto inputUser = ([](const QString &data) -> MTPInputUser { auto inputUser = ([](const QString &data) -> MTPInputUser {
auto fields = MentionNameDataToFields(data); auto fields = MentionNameDataToFields(data);
if (fields.userId == Auth().userId()) { if (fields.userId == Auth().userId()) {
@ -1546,11 +1546,11 @@ MTPVector<MTPMessageEntity> EntitiesToMTP(const EntitiesInText &entities, Conver
v.push_back(MTP_inputMessageEntityMentionName(offset, length, inputUser)); v.push_back(MTP_inputMessageEntityMentionName(offset, length, inputUser));
} }
} break; } break;
case EntityInTextBotCommand: v.push_back(MTP_messageEntityBotCommand(offset, length)); break; case EntityType::BotCommand: v.push_back(MTP_messageEntityBotCommand(offset, length)); break;
case EntityInTextBold: v.push_back(MTP_messageEntityBold(offset, length)); break; case EntityType::Bold: v.push_back(MTP_messageEntityBold(offset, length)); break;
case EntityInTextItalic: v.push_back(MTP_messageEntityItalic(offset, length)); break; case EntityType::Italic: v.push_back(MTP_messageEntityItalic(offset, length)); break;
case EntityInTextCode: v.push_back(MTP_messageEntityCode(offset, length)); break; case EntityType::Code: v.push_back(MTP_messageEntityCode(offset, length)); break;
case EntityInTextPre: v.push_back(MTP_messageEntityPre(offset, length, MTP_string(entity.data()))); break; case EntityType::Pre: v.push_back(MTP_messageEntityPre(offset, length, MTP_string(entity.data()))); break;
} }
} }
return MTP_vector<MTPMessageEntity>(std::move(v)); return MTP_vector<MTPMessageEntity>(std::move(v));
@ -1594,7 +1594,7 @@ void ParseEntities(TextWithEntities &result, int32 flags, bool rich) {
auto mMention = withMentions ? RegExpMention().match(result.text, qMax(mentionSkip, matchOffset)) : QRegularExpressionMatch(); auto mMention = withMentions ? RegExpMention().match(result.text, qMax(mentionSkip, matchOffset)) : QRegularExpressionMatch();
auto mBotCommand = withBotCommands ? RegExpBotCommand().match(result.text, matchOffset) : QRegularExpressionMatch(); auto mBotCommand = withBotCommands ? RegExpBotCommand().match(result.text, matchOffset) : QRegularExpressionMatch();
EntityInTextType lnkType = EntityInTextUrl; auto lnkType = EntityType::Url;
int32 lnkStart = 0, lnkLength = 0; int32 lnkStart = 0, lnkLength = 0;
auto domainStart = mDomain.hasMatch() ? mDomain.capturedStart() : kNotFound, auto domainStart = mDomain.hasMatch() ? mDomain.capturedStart() : kNotFound,
domainEnd = mDomain.hasMatch() ? mDomain.capturedEnd() : kNotFound, domainEnd = mDomain.hasMatch() ? mDomain.capturedEnd() : kNotFound,
@ -1683,7 +1683,7 @@ void ParseEntities(TextWithEntities &result, int32 flags, bool rich) {
continue; continue;
} }
lnkType = EntityInTextMention; lnkType = EntityType::Mention;
lnkStart = mentionStart; lnkStart = mentionStart;
lnkLength = mentionEnd - mentionStart; lnkLength = mentionEnd - mentionStart;
} else if (hashtagStart < domainStart } else if (hashtagStart < domainStart
@ -1704,7 +1704,7 @@ void ParseEntities(TextWithEntities &result, int32 flags, bool rich) {
continue; continue;
} }
lnkType = EntityInTextHashtag; lnkType = EntityType::Hashtag;
lnkStart = hashtagStart; lnkStart = hashtagStart;
lnkLength = hashtagEnd - hashtagStart; lnkLength = hashtagEnd - hashtagStart;
} else if (botCommandStart < domainStart) { } else if (botCommandStart < domainStart) {
@ -1720,7 +1720,7 @@ void ParseEntities(TextWithEntities &result, int32 flags, bool rich) {
continue; continue;
} }
lnkType = EntityInTextBotCommand; lnkType = EntityType::BotCommand;
lnkStart = botCommandStart; lnkStart = botCommandStart;
lnkLength = botCommandEnd - botCommandStart; lnkLength = botCommandEnd - botCommandStart;
} else { } else {
@ -1749,12 +1749,12 @@ void ParseEntities(TextWithEntities &result, int32 flags, bool rich) {
if (mailStart < offset) { if (mailStart < offset) {
mailStart = offset; mailStart = offset;
} }
lnkType = EntityInTextEmail; lnkType = EntityType::Email;
lnkStart = mailStart; lnkStart = mailStart;
lnkLength = domainEnd - mailStart; lnkLength = domainEnd - mailStart;
} }
} }
if (lnkType == EntityInTextUrl && !lnkLength) { if (lnkType == EntityType::Url && !lnkLength) {
if (!isProtocolValid || !isTopDomainValid) { if (!isProtocolValid || !isTopDomainValid) {
matchOffset = domainEnd; matchOffset = domainEnd;
continue; continue;
@ -1803,7 +1803,7 @@ void ParseEntities(TextWithEntities &result, int32 flags, bool rich) {
newEntities.push_back(entity); newEntities.push_back(entity);
} }
if (lnkStart >= existingEntityEnd) { if (lnkStart >= existingEntityEnd) {
result.entities.push_back(EntityInText(lnkType, lnkStart, lnkLength)); result.entities.push_back({ lnkType, lnkStart, lnkLength });
} }
offset = matchOffset = lnkStart + lnkLength; offset = matchOffset = lnkStart + lnkLength;
@ -1914,7 +1914,9 @@ void Trim(TextWithEntities &result) {
return; return;
} }
auto firstMonospaceOffset = EntityInText::firstMonospaceOffset(result.entities, result.text.size()); const auto firstMonospaceOffset = EntityInText::FirstMonospaceOffset(
result.entities,
result.text.size());
// left trim // left trim
for (auto s = result.text.data(), ch = s, e = s + result.text.size(); ch != e; ++ch) { for (auto s = result.text.data(), ch = s, e = s + result.text.size(); ch != e; ++ch) {
@ -1985,8 +1987,39 @@ QString TagsMimeType() {
return qsl("application/x-td-field-tags"); return qsl("application/x-td-field-tags");
} }
QString TagsTextMimeType() {
return qsl("application/x-td-field-text");
}
} // namespace TextUtilities } // namespace TextUtilities
EntityInText::EntityInText(
EntityType type,
int offset,
int length,
const QString &data)
: _type(type)
, _offset(offset)
, _length(length)
, _data(data) {
}
int EntityInText::FirstMonospaceOffset(
const EntitiesInText &entities,
int textLength) {
auto &&monospace = ranges::view::all(
entities
) | ranges::view::filter([](const EntityInText & entity) {
return (entity.type() == EntityType::Pre)
|| (entity.type() == EntityType::Code);
});
const auto i = ranges::max_element(
monospace,
std::greater<>(),
&EntityInText::offset);
return (i == monospace.end()) ? textLength : i->offset();
}
namespace Lang { namespace Lang {
TextWithEntities ReplaceTag<TextWithEntities>::Call(TextWithEntities &&original, ushort tag, const TextWithEntities &replacement) { TextWithEntities ReplaceTag<TextWithEntities>::Call(TextWithEntities &&original, ushort tag, const TextWithEntities &replacement) {
@ -2014,7 +2047,7 @@ TextWithEntities ReplaceTag<TextWithEntities>::Call(TextWithEntities &&original,
newOffset = snap(newOffset, replacementPosition, replacementEnd); newOffset = snap(newOffset, replacementPosition, replacementEnd);
newEnd = snap(newEnd, replacementPosition, replacementEnd); newEnd = snap(newEnd, replacementPosition, replacementEnd);
if (auto newLength = newEnd - newOffset) { if (auto newLength = newEnd - newOffset) {
result.entities.push_back(EntityInText(replacementEntity->type(), newOffset, newLength, replacementEntity->data())); result.entities.push_back({ replacementEntity->type(), newOffset, newLength, replacementEntity->data() });
} }
++replacementEntity; ++replacementEntity;
} }
@ -2038,7 +2071,7 @@ TextWithEntities ReplaceTag<TextWithEntities>::Call(TextWithEntities &&original,
// Add a modified original entity. // Add a modified original entity.
if (auto length = end - offset) { if (auto length = end - offset) {
result.entities.push_back(EntityInText(entity.type(), offset, length, entity.data())); result.entities.push_back({ entity.type(), offset, length, entity.data() });
} }
} }
// Add the remaining replacement entities. // Add the remaining replacement entities.

View File

@ -7,22 +7,22 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#pragma once #pragma once
enum EntityInTextType { enum class EntityType {
EntityInTextInvalid = 0, Invalid = 0,
EntityInTextUrl, Url,
EntityInTextCustomUrl, CustomUrl,
EntityInTextEmail, Email,
EntityInTextHashtag, Hashtag,
EntityInTextCashtag, Cashtag,
EntityInTextMention, Mention,
EntityInTextMentionName, MentionName,
EntityInTextBotCommand, BotCommand,
EntityInTextBold, Bold,
EntityInTextItalic, Italic,
EntityInTextCode, // inline Code, // inline
EntityInTextPre, // block Pre, // block
}; };
class EntityInText; class EntityInText;
@ -30,14 +30,13 @@ using EntitiesInText = QList<EntityInText>;
class EntityInText { class EntityInText {
public: public:
EntityInText(EntityInTextType type, int offset, int length, const QString &data = QString()) EntityInText(
: _type(type) EntityType type,
, _offset(offset) int offset,
, _length(length) int length,
, _data(data) { const QString &data = QString());
}
EntityInTextType type() const { EntityType type() const {
return _type; return _type;
} }
int offset() const { int offset() const {
@ -79,23 +78,18 @@ public:
} }
} }
static int firstMonospaceOffset(const EntitiesInText &entities, int textLength) { static int FirstMonospaceOffset(
int result = textLength; const EntitiesInText &entities,
for_const (auto &entity, entities) { int textLength);
if (entity.type() == EntityInTextPre || entity.type() == EntityInTextCode) {
accumulate_min(result, entity.offset());
}
}
return result;
}
explicit operator bool() const { explicit operator bool() const {
return type() != EntityInTextInvalid; return type() != EntityType::Invalid;
} }
private: private:
EntityInTextType _type; EntityType _type = EntityType::Invalid;
int _offset, _length; int _offset = 0;
int _length = 0;
QString _data; QString _data;
}; };
@ -118,6 +112,39 @@ struct TextWithEntities {
bool empty() const { bool empty() const {
return text.isEmpty(); return text.isEmpty();
} }
void reserve(int size, int entitiesCount = 0) {
text.reserve(size);
entities.reserve(entitiesCount);
}
TextWithEntities &append(TextWithEntities &&other) {
const auto shift = text.size();
for (auto &entity : other.entities) {
entity.shiftRight(shift);
}
text.append(other.text);
entities.append(other.entities);
return *this;
}
TextWithEntities &append(const QString &other) {
text.append(other);
return *this;
}
TextWithEntities &append(QLatin1String other) {
text.append(other);
return *this;
}
TextWithEntities &append(QChar other) {
text.append(other);
return *this;
}
static TextWithEntities Simple(const QString &simple) {
auto result = TextWithEntities();
result.text = simple;
return result;
}
}; };
inline bool operator==( inline bool operator==(
@ -132,6 +159,57 @@ inline bool operator!=(
return !(a == b); return !(a == b);
} }
struct TextForMimeData {
QString expanded;
TextWithEntities rich;
bool empty() const {
return expanded.isEmpty();
}
void reserve(int size, int entitiesCount = 0) {
expanded.reserve(size);
rich.reserve(size, entitiesCount);
}
TextForMimeData &append(TextForMimeData &&other) {
expanded.append(other.expanded);
rich.append(std::move(other.rich));
return *this;
}
TextForMimeData &append(TextWithEntities &&other) {
expanded.append(other.text);
rich.append(std::move(other));
return *this;
}
TextForMimeData &append(const QString &other) {
expanded.append(other);
rich.append(other);
return *this;
}
TextForMimeData &append(QLatin1String other) {
expanded.append(other);
rich.append(other);
return *this;
}
TextForMimeData &append(QChar other) {
expanded.append(other);
rich.append(other);
return *this;
}
static TextForMimeData Rich(TextWithEntities &&rich) {
auto result = TextForMimeData();
result.expanded = rich.text;
result.rich = std::move(rich);
return result;
}
static TextForMimeData Simple(const QString &simple) {
auto result = TextForMimeData();
result.expanded = result.rich.text = simple;
return result;
}
};
enum { enum {
TextParseMultiline = 0x001, TextParseMultiline = 0x001,
TextParseLinks = 0x002, TextParseLinks = 0x002,
@ -194,15 +272,6 @@ QString MarkdownCodeBadAfter();
QString MarkdownPreGoodBefore(); QString MarkdownPreGoodBefore();
QString MarkdownPreBadAfter(); QString MarkdownPreBadAfter();
inline void Append(TextWithEntities &to, TextWithEntities &&append) {
auto entitiesShiftRight = to.text.size();
for (auto &entity : append.entities) {
entity.shiftRight(entitiesShiftRight);
}
to.text += append.text;
to.entities += append.entities;
}
// Text preprocess. // Text preprocess.
QString Clean(const QString &text); QString Clean(const QString &text);
QString EscapeForRichParsing(const QString &text); QString EscapeForRichParsing(const QString &text);
@ -265,6 +334,7 @@ void ApplyServerCleaning(TextWithEntities &result);
QByteArray SerializeTags(const TextWithTags::Tags &tags); QByteArray SerializeTags(const TextWithTags::Tags &tags);
TextWithTags::Tags DeserializeTags(QByteArray data, int textLength); TextWithTags::Tags DeserializeTags(QByteArray data, int textLength);
QString TagsMimeType(); QString TagsMimeType();
QString TagsTextMimeType();
} // namespace TextUtilities } // namespace TextUtilities

View File

@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/emoji_config.h" #include "ui/emoji_config.h"
#include "emoji_suggestions_data.h" #include "emoji_suggestions_data.h"
#include "chat_helpers/emoji_suggestions_helper.h" #include "chat_helpers/emoji_suggestions_helper.h"
#include "chat_helpers/message_field.h" // ConvertTextTagsToEntities
#include "window/themes/window_theme.h" #include "window/themes/window_theme.h"
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
#include "data/data_user.h" #include "data/data_user.h"
@ -759,6 +760,33 @@ struct FormattingAction {
}; };
QString ExpandCustomLinks(const TextWithTags &text) {
const auto entities = ConvertTextTagsToEntities(text.tags);
auto &&urls = ranges::view::all(
entities
) | ranges::view::filter([](const EntityInText &entity) {
return entity.type() == EntityType::CustomUrl;
});
const auto &original = text.text;
if (urls.begin() == urls.end()) {
return original;
}
auto result = QString();
auto offset = 0;
for (const auto &entity : urls) {
const auto till = entity.offset() + entity.length();
if (till > offset) {
result.append(original.midRef(offset, till - offset));
}
result.append(qstr(" (")).append(entity.data()).append(')');
offset = till;
}
if (original.size() > offset) {
result.append(original.midRef(offset));
}
return result;
}
} // namespace } // namespace
const QString InputField::kTagBold = qsl("**"); const QString InputField::kTagBold = qsl("**");
@ -2309,13 +2337,16 @@ QMimeData *InputField::createMimeDataFromSelectionInner() const {
const auto end = cursor.selectionEnd(); const auto end = cursor.selectionEnd();
if (end > start) { if (end > start) {
auto textWithTags = getTextWithTagsPart(start, end); auto textWithTags = getTextWithTagsPart(start, end);
result->setText(textWithTags.text); result->setText(ExpandCustomLinks(textWithTags));
if (!textWithTags.tags.isEmpty()) { if (!textWithTags.tags.isEmpty()) {
if (_tagMimeProcessor) { if (_tagMimeProcessor) {
for (auto &tag : textWithTags.tags) { for (auto &tag : textWithTags.tags) {
tag.id = _tagMimeProcessor->mimeTagFromTag(tag.id); tag.id = _tagMimeProcessor->mimeTagFromTag(tag.id);
} }
} }
result->setData(
TextUtilities::TagsTextMimeType(),
textWithTags.text.toUtf8());
result->setData( result->setData(
TextUtilities::TagsMimeType(), TextUtilities::TagsMimeType(),
TextUtilities::SerializeTags(textWithTags.tags)); TextUtilities::SerializeTags(textWithTags.tags));
@ -3368,21 +3399,27 @@ void InputField::insertFromMimeDataInner(const QMimeData *source) {
&& _mimeDataHook(source, MimeAction::Insert)) { && _mimeDataHook(source, MimeAction::Insert)) {
return; return;
} }
auto mime = TextUtilities::TagsMimeType(); const auto text = [&] {
auto text = source->text(); const auto textMime = TextUtilities::TagsTextMimeType();
if (source->hasFormat(mime)) { const auto tagsMime = TextUtilities::TagsMimeType();
auto tagsData = source->data(mime); if (!source->hasFormat(textMime) || !source->hasFormat(tagsMime)) {
_insertedTags.clear();
return source->text();
}
auto result = QString::fromUtf8(source->data(textMime));
_insertedTags = TextUtilities::DeserializeTags( _insertedTags = TextUtilities::DeserializeTags(
tagsData, source->data(tagsMime),
text.size()); result.size());
_insertedTagsAreFromMime = true; _insertedTagsAreFromMime = true;
} else { return result;
_insertedTags.clear(); }();
}
auto cursor = textCursor(); auto cursor = textCursor();
_realInsertPosition = cursor.selectionStart(); _realInsertPosition = cursor.selectionStart();
_realCharsAdded = text.size(); _realCharsAdded = text.size();
_inner->QTextEdit::insertFromMimeData(source); if (_realCharsAdded > 0) {
cursor.insertFragment(QTextDocumentFragment::fromPlainText(text));
}
ensureCursorVisible();
if (!_inDrop) { if (!_inDrop) {
_insertedTags.clear(); _insertedTags.clear();
_realInsertPosition = -1; _realInsertPosition = -1;

View File

@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/widgets/labels.h" #include "ui/widgets/labels.h"
#include "ui/widgets/popup_menu.h" #include "ui/widgets/popup_menu.h"
#include "chat_helpers/message_field.h" // SetClipboardText/MimeDataFromText
#include "mainwindow.h" #include "mainwindow.h"
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
@ -569,12 +570,12 @@ void FlatLabel::showContextMenu(QContextMenuEvent *e, ContextMenuReason reason)
void FlatLabel::onCopySelectedText() { void FlatLabel::onCopySelectedText() {
const auto selection = _selection.empty() ? (_contextMenu ? _savedSelection : _selection) : _selection; const auto selection = _selection.empty() ? (_contextMenu ? _savedSelection : _selection) : _selection;
if (!selection.empty()) { if (!selection.empty()) {
QApplication::clipboard()->setText(_text.toString(selection, ExpandLinksAll)); SetClipboardText(_text.toTextForMimeData(selection));
} }
} }
void FlatLabel::onCopyContextText() { void FlatLabel::onCopyContextText() {
QApplication::clipboard()->setText(_text.toString(AllTextSelection, ExpandLinksAll)); SetClipboardText(_text.toTextForMimeData());
} }
void FlatLabel::onTouchSelect() { void FlatLabel::onTouchSelect() {
@ -599,18 +600,18 @@ void FlatLabel::onExecuteDrag() {
} }
} }
ClickHandlerPtr pressedHandler = ClickHandler::getPressed(); const auto pressedHandler = ClickHandler::getPressed();
QString selectedText; const auto selectedText = [&] {
if (uponSelected) { if (uponSelected) {
selectedText = _text.toString(_selection, ExpandLinksAll); return _text.toTextForMimeData(_selection);
} else if (pressedHandler) { } else if (pressedHandler) {
selectedText = pressedHandler->dragText(); return TextForMimeData::Simple(pressedHandler->dragText());
} }
if (!selectedText.isEmpty()) { return TextForMimeData();
auto mimeData = new QMimeData(); }();
mimeData->setText(selectedText); if (auto mimeData = MimeDataFromText(selectedText)) {
auto drag = new QDrag(App::wnd()); auto drag = new QDrag(App::wnd());
drag->setMimeData(mimeData); drag->setMimeData(mimeData.release());
drag->exec(Qt::CopyAction); drag->exec(Qt::CopyAction);
// We don't receive mouseReleaseEvent when drag is finished. // We don't receive mouseReleaseEvent when drag is finished.