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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

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
//}
}
//if (auto mimeData = MimeDataFromTextWithEntities(sel)) {
//if (auto mimeData = MimeDataFromText(sel)) {
// clearDragSelection();
// _widget->noSelectingScroll();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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