Everywhere TextWithTags and TextWithEntities are used.

Copy tags from messages to clipboard, to drag mime data.
Sorting entities while processing (links, monospace, mentions).
This commit is contained in:
John Preston 2016-05-06 20:33:48 +03:00
parent 463450e607
commit 3e5f51f45a
19 changed files with 542 additions and 369 deletions

View File

@ -968,7 +968,9 @@ namespace {
peerId = peerFromUser(m.vfrom_id); peerId = peerFromUser(m.vfrom_id);
} }
if (HistoryItem *existing = App::histItemById(peerToChannel(peerId), m.vid.v)) { if (HistoryItem *existing = App::histItemById(peerToChannel(peerId), m.vid.v)) {
existing->setText(qs(m.vmessage), m.has_entities() ? entitiesFromMTP(m.ventities.c_vector().v) : EntitiesInText()); auto text = qs(m.vmessage);
auto entities = m.has_entities() ? entitiesFromMTP(m.ventities.c_vector().v) : EntitiesInText();
existing->setText({ text, entities });
existing->updateMedia(m.has_media() ? (&m.vmedia) : nullptr); existing->updateMedia(m.has_media() ? (&m.vmedia) : nullptr);
existing->setViewsCount(m.has_views() ? m.vviews.v : -1); existing->setViewsCount(m.has_views() ? m.vviews.v : -1);
existing->addToOverview(AddToOverviewNew); existing->addToOverview(AddToOverviewNew);

View File

@ -414,7 +414,7 @@ EditCaptionBox::EditCaptionBox(HistoryItem *msg) : AbstractBox(st::boxWideWidth)
image = doc->thumb; image = doc->thumb;
} break; } break;
} }
caption = media->getCaption(); caption = media->getCaption().text;
} }
if ((!_animated && (dimensions.isEmpty() || doc)) || image->isNull()) { if ((!_animated && (dimensions.isEmpty() || doc)) || image->isNull()) {
_animated = false; _animated = false;
@ -492,7 +492,8 @@ EditCaptionBox::EditCaptionBox(HistoryItem *msg) : AbstractBox(st::boxWideWidth)
_field->setMaxLength(MaxPhotoCaption); _field->setMaxLength(MaxPhotoCaption);
_field->setCtrlEnterSubmit(CtrlEnterSubmitBoth); _field->setCtrlEnterSubmit(CtrlEnterSubmitBoth);
} else { } else {
QString text = textApplyEntities(msg->originalText(), msg->originalEntities()); auto original = msg->originalText();
QString text = textApplyEntities(original.text, original.entities);
_field = new InputArea(this, st::editTextArea, lang(lng_photo_caption), text); _field = new InputArea(this, st::editTextArea, lang(lng_photo_caption), text);
// _field->setMaxLength(MaxMessageSize); // entities can make text in input field larger but still valid // _field->setMaxLength(MaxMessageSize); // entities can make text in input field larger but still valid
_field->setCtrlEnterSubmit(cCtrlEnter() ? CtrlEnterSubmitCtrlEnter : CtrlEnterSubmitEnter); _field->setCtrlEnterSubmit(cCtrlEnter() ? CtrlEnterSubmitCtrlEnter : CtrlEnterSubmitEnter);

View File

@ -66,6 +66,12 @@ QString ClickHandler::getExpandedLinkText(ExpandLinksMode mode, const QStringRef
return QString(); return QString();
} }
EntityInText ClickHandler::getEntityInText(int offset, const QStringRef &textPart) const { TextWithEntities ClickHandler::getExpandedLinkTextWithEntities(ExpandLinksMode mode, int entityOffset, const QStringRef &textPart) const {
return EntityInText(EntityInTextInvalid, offset, 0); return { QString(), EntitiesInText() };
}
TextWithEntities ClickHandler::simpleTextWithEntity(const EntityInText &entity) const {
TextWithEntities result;
result.entities.push_back(entity);
return result;
} }

View File

@ -42,9 +42,9 @@ protected:
}; };
class EntityInText; class EntityInText;
struct TextWithEntities;
class ClickHandler { class ClickHandler {
public: public:
virtual ~ClickHandler() { virtual ~ClickHandler() {
} }
@ -71,8 +71,7 @@ public:
// This method returns empty string if just textPart should be used (nothing to expand). // This method returns empty string if just textPart should be used (nothing to expand).
virtual QString getExpandedLinkText(ExpandLinksMode mode, const QStringRef &textPart) const; virtual QString getExpandedLinkText(ExpandLinksMode mode, const QStringRef &textPart) const;
virtual TextWithEntities getExpandedLinkTextWithEntities(ExpandLinksMode mode, int entityOffset, const QStringRef &textPart) const;
virtual EntityInText getEntityInText(int offset, const QStringRef &textPart) const;
// This method should be called on mouse over a click handler. // This method should be called on mouse over a click handler.
// It returns true if the active handler was changed or false otherwise. // It returns true if the active handler was changed or false otherwise.
@ -152,8 +151,12 @@ public:
} }
} }
private: 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> _active;
static NeverFreedPointer<ClickHandlerPtr> _pressed; static NeverFreedPointer<ClickHandlerPtr> _pressed;
static ClickHandlerHost *_activeHost; static ClickHandlerHost *_activeHost;

View File

@ -75,18 +75,23 @@ void UrlClickHandler::doOpen(QString url) {
} }
QString UrlClickHandler::getExpandedLinkText(ExpandLinksMode mode, const QStringRef &textPart) const { QString UrlClickHandler::getExpandedLinkText(ExpandLinksMode mode, const QStringRef &textPart) const {
if (mode == ExpandLinksNone) { QString result;
return QString(); if (mode != ExpandLinksNone) {
result = _originalUrl;
} }
return _originalUrl; return result;
} }
EntityInText UrlClickHandler::getEntityInText(int offset, const QStringRef &textPart) const { TextWithEntities UrlClickHandler::getExpandedLinkTextWithEntities(ExpandLinksMode mode, int entityOffset, const QStringRef &textPart) const {
auto u = _originalUrl; TextWithEntities result;
if (isEmail(u)) { auto entityType = isEmail(_originalUrl) ? EntityInTextEmail : EntityInTextUrl;
return EntityInText(EntityInTextUrl, offset, u.size()); int entityLength = textPart.size();
if (mode != ExpandLinksNone) {
result.text = _originalUrl;
entityLength = _originalUrl.size();
} }
return EntityInText(EntityInTextUrl, offset, u.size()); result.entities.push_back({ entityType, entityOffset, entityLength });
return result;
} }
void HiddenUrlClickHandler::onClick(Qt::MouseButton button) const { void HiddenUrlClickHandler::onClick(Qt::MouseButton button) const {
@ -102,14 +107,20 @@ void HiddenUrlClickHandler::onClick(Qt::MouseButton button) const {
} }
QString HiddenUrlClickHandler::getExpandedLinkText(ExpandLinksMode mode, const QStringRef &textPart) const { QString HiddenUrlClickHandler::getExpandedLinkText(ExpandLinksMode mode, const QStringRef &textPart) const {
if (mode != ExpandLinksAll) { QString result;
return QString(); if (mode == ExpandLinksAll) {
result = textPart.toString() + qsl(" (") + url() + ')';
} }
return textPart.toString() + qsl(" (") + url() + ')'; return result;
} }
EntityInText HiddenUrlClickHandler::getEntityInText(int offset, const QStringRef &textPart) const { TextWithEntities HiddenUrlClickHandler::getExpandedLinkTextWithEntities(ExpandLinksMode mode, int entityOffset, const QStringRef &textPart) const {
return EntityInText(EntityInTextCustomUrl, offset, textPart.size(), url()); TextWithEntities result;
result.entities.push_back({ EntityInTextCustomUrl, entityOffset, textPart.size(), url() });
if (mode == ExpandLinksAll) {
result.text = textPart.toString() + qsl(" (") + url() + ')';
}
return result;
} }
QString MentionClickHandler::copyToClipboardContextItemText() const { QString MentionClickHandler::copyToClipboardContextItemText() const {
@ -122,8 +133,8 @@ void MentionClickHandler::onClick(Qt::MouseButton button) const {
} }
} }
EntityInText MentionClickHandler::getEntityInText(int offset, const QStringRef &textPart) const { TextWithEntities MentionClickHandler::getExpandedLinkTextWithEntities(ExpandLinksMode mode, int entityOffset, const QStringRef &textPart) const {
return EntityInText(EntityInTextMention, offset, textPart.size()); return simpleTextWithEntity({ EntityInTextMention, entityOffset, textPart.size() });
} }
void MentionNameClickHandler::onClick(Qt::MouseButton button) const { void MentionNameClickHandler::onClick(Qt::MouseButton button) const {
@ -134,9 +145,9 @@ void MentionNameClickHandler::onClick(Qt::MouseButton button) const {
} }
} }
EntityInText MentionNameClickHandler::getEntityInText(int offset, const QStringRef &textPart) const { TextWithEntities MentionNameClickHandler::getExpandedLinkTextWithEntities(ExpandLinksMode mode, int entityOffset, const QStringRef &textPart) const {
auto data = QString::number(_userId) + '.' + QString::number(_accessHash); auto data = QString::number(_userId) + '.' + QString::number(_accessHash);
return EntityInText(EntityInTextMentionName, offset, textPart.size(), data); return simpleTextWithEntity({ EntityInTextMentionName, entityOffset, textPart.size(), data });
} }
QString MentionNameClickHandler::tooltip() const { QString MentionNameClickHandler::tooltip() const {
@ -159,8 +170,8 @@ void HashtagClickHandler::onClick(Qt::MouseButton button) const {
} }
} }
EntityInText HashtagClickHandler::getEntityInText(int offset, const QStringRef &textPart) const { TextWithEntities HashtagClickHandler::getExpandedLinkTextWithEntities(ExpandLinksMode mode, int entityOffset, const QStringRef &textPart) const {
return EntityInText(EntityInTextHashtag, offset, textPart.size()); return simpleTextWithEntity({ EntityInTextHashtag, entityOffset, textPart.size() });
} }
void BotCommandClickHandler::onClick(Qt::MouseButton button) const { void BotCommandClickHandler::onClick(Qt::MouseButton button) const {
@ -180,6 +191,6 @@ void BotCommandClickHandler::onClick(Qt::MouseButton button) const {
} }
} }
EntityInText BotCommandClickHandler::getEntityInText(int offset, const QStringRef &textPart) const { TextWithEntities BotCommandClickHandler::getExpandedLinkTextWithEntities(ExpandLinksMode mode, int entityOffset, const QStringRef &textPart) const {
return EntityInText(EntityInTextHashtag, offset, textPart.size()); return simpleTextWithEntity({ EntityInTextHashtag, entityOffset, textPart.size() });
} }

View File

@ -70,7 +70,7 @@ public:
} }
QString getExpandedLinkText(ExpandLinksMode mode, const QStringRef &textPart) const override; QString getExpandedLinkText(ExpandLinksMode mode, const QStringRef &textPart) const override;
EntityInText getEntityInText(int offset, const QStringRef &textPart) const override; TextWithEntities getExpandedLinkTextWithEntities(ExpandLinksMode mode, int entityOffset, const QStringRef &textPart) const override;
static void doOpen(QString url); static void doOpen(QString url);
void onClick(Qt::MouseButton button) const override { void onClick(Qt::MouseButton button) const override {
@ -118,7 +118,7 @@ public:
void onClick(Qt::MouseButton button) const override; void onClick(Qt::MouseButton button) const override;
QString getExpandedLinkText(ExpandLinksMode mode, const QStringRef &textPart) const override; QString getExpandedLinkText(ExpandLinksMode mode, const QStringRef &textPart) const override;
EntityInText getEntityInText(int offset, const QStringRef &textPart) const override; TextWithEntities getExpandedLinkTextWithEntities(ExpandLinksMode mode, int entityOffset, const QStringRef &textPart) const override;
}; };
@ -135,7 +135,7 @@ public:
QString copyToClipboardContextItemText() const override; QString copyToClipboardContextItemText() const override;
EntityInText getEntityInText(int offset, const QStringRef &textPart) const override; TextWithEntities getExpandedLinkTextWithEntities(ExpandLinksMode mode, int entityOffset, const QStringRef &textPart) const override;
protected: protected:
QString url() const override { QString url() const override {
@ -157,7 +157,7 @@ public:
void onClick(Qt::MouseButton button) const override; void onClick(Qt::MouseButton button) const override;
EntityInText getEntityInText(int offset, const QStringRef &textPart) const override; TextWithEntities getExpandedLinkTextWithEntities(ExpandLinksMode mode, int entityOffset, const QStringRef &textPart) const override;
QString tooltip() const override; QString tooltip() const override;
@ -181,7 +181,7 @@ public:
QString copyToClipboardContextItemText() const override; QString copyToClipboardContextItemText() const override;
EntityInText getEntityInText(int offset, const QStringRef &textPart) const override; TextWithEntities getExpandedLinkTextWithEntities(ExpandLinksMode mode, int entityOffset, const QStringRef &textPart) const override;
protected: protected:
QString url() const override { QString url() const override {
@ -204,7 +204,7 @@ public:
return _cmd; return _cmd;
} }
EntityInText getEntityInText(int offset, const QStringRef &textPart) const override; TextWithEntities getExpandedLinkTextWithEntities(ExpandLinksMode mode, int entityOffset, const QStringRef &textPart) const override;
protected: protected:
QString url() const override { QString url() const override {

View File

@ -1032,7 +1032,7 @@ HistoryItem *History::createItem(const MTPMessage &msg, bool applyServiceAction,
EntitiesInText entities; EntitiesInText entities;
textParseEntities(text, _historyTextNoMonoOptions.flags, &entities); textParseEntities(text, _historyTextNoMonoOptions.flags, &entities);
entities.push_front(EntityInText(EntityInTextItalic, 0, text.size())); entities.push_front(EntityInText(EntityInTextItalic, 0, text.size()));
result = HistoryMessage::create(this, m.vid.v, m.vflags.v, m.vreply_to_msg_id.v, m.vvia_bot_id.v, date(m.vdate), m.vfrom_id.v, text, entities); result = HistoryMessage::create(this, m.vid.v, m.vflags.v, m.vreply_to_msg_id.v, m.vvia_bot_id.v, date(m.vdate), m.vfrom_id.v, { text, entities });
} else if (badMedia) { } else if (badMedia) {
result = HistoryService::create(this, m.vid.v, date(m.vdate), lang(lng_message_empty), m.vflags.v, m.has_from_id() ? m.vfrom_id.v : 0); result = HistoryService::create(this, m.vid.v, date(m.vdate), lang(lng_message_empty), m.vflags.v, m.has_from_id() ? m.vfrom_id.v : 0);
} else { } else {
@ -3095,22 +3095,21 @@ void HistoryItem::setId(MsgId newId) {
} }
bool HistoryItem::canEdit(const QDateTime &cur) const { bool HistoryItem::canEdit(const QDateTime &cur) const {
int32 s = date.secsTo(cur);
auto channel = _history->peer->asChannel(); auto channel = _history->peer->asChannel();
if (!channel || id < 0 || date.secsTo(cur) >= Global::EditTimeLimit()) return false; if (!channel || id < 0 || date.secsTo(cur) >= Global::EditTimeLimit()) return false;
if (const HistoryMessage *msg = toHistoryMessage()) { if (auto msg = toHistoryMessage()) {
if (msg->Has<HistoryMessageVia>() || msg->Has<HistoryMessageForwarded>()) return false; if (msg->Has<HistoryMessageVia>() || msg->Has<HistoryMessageForwarded>()) return false;
if (HistoryMedia *media = msg->getMedia()) { if (HistoryMedia *media = msg->getMedia()) {
HistoryMediaType t = media->type(); auto type = media->type();
if (t != MediaTypePhoto && if (type != MediaTypePhoto &&
t != MediaTypeVideo && type != MediaTypeVideo &&
t != MediaTypeFile && type != MediaTypeFile &&
t != MediaTypeGif && type != MediaTypeGif &&
t != MediaTypeMusicFile && type != MediaTypeMusicFile &&
t != MediaTypeVoiceFile && type != MediaTypeVoiceFile &&
t != MediaTypeWebPage) { type != MediaTypeWebPage) {
return false; return false;
} }
} }
@ -3304,18 +3303,24 @@ int32 gifMaxStatusWidth(DocumentData *document) {
return result; return result;
} }
QString captionedSelectedText(const QString &attachType, const Text &caption, TextSelection selection) { TextWithEntities captionedSelectedText(const QString &attachType, const Text &caption, TextSelection selection) {
if (selection != FullSelection) { if (selection != FullSelection) {
return caption.original(selection, ExpandLinksAll); return caption.originalTextWithEntities(selection, ExpandLinksAll);
} }
QString result;
result.reserve(5 + attachType.size() + caption.length()); TextWithEntities result, original;
result.append(qstr("[ ")).append(attachType).append(qstr(" ]"));
if (!caption.isEmpty()) { if (!caption.isEmpty()) {
result.append(qstr("\n")).append(caption.original(AllTextSelection)); original = caption.originalTextWithEntities(AllTextSelection, ExpandLinksAll);
}
result.text.reserve(5 + attachType.size() + original.text.size());
result.text.append(qstr("[ ")).append(attachType).append(qstr(" ]"));
if (!caption.isEmpty()) {
result.text.append(qstr("\n"));
appendTextWithEntities(result, std_::move(original));
} }
return result; return result;
} }
} // namespace } // namespace
void HistoryFileMedia::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) { void HistoryFileMedia::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) {
@ -3735,10 +3740,10 @@ void HistoryPhoto::detachFromParent() {
} }
QString HistoryPhoto::inDialogsText() const { QString HistoryPhoto::inDialogsText() const {
return _caption.isEmpty() ? lang(lng_in_dlg_photo) : _caption.original(AllTextSelection, ExpandLinksNone); return _caption.isEmpty() ? lang(lng_in_dlg_photo) : _caption.originalText(AllTextSelection, ExpandLinksNone);
} }
QString HistoryPhoto::selectedText(TextSelection selection) const { TextWithEntities HistoryPhoto::selectedText(TextSelection selection) const {
return captionedSelectedText(lang(lng_in_dlg_photo), _caption, selection); return captionedSelectedText(lang(lng_in_dlg_photo), _caption, selection);
} }
@ -3981,10 +3986,10 @@ void HistoryVideo::setStatusSize(int32 newSize) const {
} }
QString HistoryVideo::inDialogsText() const { QString HistoryVideo::inDialogsText() const {
return _caption.isEmpty() ? lang(lng_in_dlg_video) : _caption.original(AllTextSelection, ExpandLinksNone); return _caption.isEmpty() ? lang(lng_in_dlg_video) : _caption.originalText(AllTextSelection, ExpandLinksNone);
} }
QString HistoryVideo::selectedText(TextSelection selection) const { TextWithEntities HistoryVideo::selectedText(TextSelection selection) const {
return captionedSelectedText(lang(lng_in_dlg_video), _caption, selection); return captionedSelectedText(lang(lng_in_dlg_video), _caption, selection);
} }
@ -4472,13 +4477,13 @@ QString HistoryDocument::inDialogsText() const {
} }
if (auto captioned = Get<HistoryDocumentCaptioned>()) { if (auto captioned = Get<HistoryDocumentCaptioned>()) {
if (!captioned->_caption.isEmpty()) { if (!captioned->_caption.isEmpty()) {
result.append(' ').append(captioned->_caption.original(AllTextSelection, ExpandLinksNone)); result.append(' ').append(captioned->_caption.originalText(AllTextSelection, ExpandLinksNone));
} }
} }
return result; return result;
} }
QString HistoryDocument::selectedText(TextSelection selection) const { TextWithEntities HistoryDocument::selectedText(TextSelection selection) const {
const Text emptyCaption; const Text emptyCaption;
const Text *caption = &emptyCaption; const Text *caption = &emptyCaption;
if (auto captioned = Get<HistoryDocumentCaptioned>()) { if (auto captioned = Get<HistoryDocumentCaptioned>()) {
@ -4930,10 +4935,10 @@ HistoryTextState HistoryGif::getState(int x, int y, HistoryStateRequest request)
} }
QString HistoryGif::inDialogsText() const { QString HistoryGif::inDialogsText() const {
return qsl("GIF") + (_caption.isEmpty() ? QString() : (' ' + _caption.original(AllTextSelection, ExpandLinksNone))); return qsl("GIF") + (_caption.isEmpty() ? QString() : (' ' + _caption.originalText(AllTextSelection, ExpandLinksNone)));
} }
QString HistoryGif::selectedText(TextSelection selection) const { TextWithEntities HistoryGif::selectedText(TextSelection selection) const {
return captionedSelectedText(qsl("GIF"), _caption, selection); return captionedSelectedText(qsl("GIF"), _caption, selection);
} }
@ -5250,11 +5255,11 @@ QString HistorySticker::inDialogsText() const {
return _emoji.isEmpty() ? lang(lng_in_dlg_sticker) : lng_in_dlg_sticker_emoji(lt_emoji, _emoji); return _emoji.isEmpty() ? lang(lng_in_dlg_sticker) : lng_in_dlg_sticker_emoji(lt_emoji, _emoji);
} }
QString HistorySticker::selectedText(TextSelection selection) const { TextWithEntities HistorySticker::selectedText(TextSelection selection) const {
if (selection != FullSelection) { if (selection != FullSelection) {
return QString(); return TextWithEntities();
} }
return qsl("[ ") + inDialogsText() + qsl(" ]"); return { qsl("[ ") + inDialogsText() + qsl(" ]"), EntitiesInText() };
} }
void HistorySticker::attachToParent() { void HistorySticker::attachToParent() {
@ -5435,11 +5440,11 @@ QString HistoryContact::inDialogsText() const {
return lang(lng_in_dlg_contact); return lang(lng_in_dlg_contact);
} }
QString HistoryContact::selectedText(TextSelection selection) const { TextWithEntities HistoryContact::selectedText(TextSelection selection) const {
if (selection != FullSelection) { if (selection != FullSelection) {
return QString(); return TextWithEntities();
} }
return qsl("[ ") + lang(lng_in_dlg_contact) + qsl(" ]\n") + _name.original() + '\n' + _phone; return { qsl("[ ") + lang(lng_in_dlg_contact) + qsl(" ]\n") + _name.originalText() + '\n' + _phone, EntitiesInText() };
} }
void HistoryContact::attachToParent() { void HistoryContact::attachToParent() {
@ -5956,18 +5961,21 @@ QString HistoryWebPage::inDialogsText() const {
return QString(); return QString();
} }
QString HistoryWebPage::selectedText(TextSelection selection) const { TextWithEntities HistoryWebPage::selectedText(TextSelection selection) const {
if (selection == FullSelection) { if (selection == FullSelection) {
return QString(); return TextWithEntities();
} }
auto titleResult = _title.original(selection, ExpandLinksAll); auto titleResult = _title.originalTextWithEntities(selection, ExpandLinksAll);
auto descriptionResult = _description.original(toDescriptionSelection(selection), ExpandLinksAll); auto descriptionResult = _description.originalTextWithEntities(toDescriptionSelection(selection), ExpandLinksAll);
if (titleResult.isEmpty()) { if (titleResult.text.isEmpty()) {
return descriptionResult; return descriptionResult;
} else if (descriptionResult.isEmpty()) { } else if (descriptionResult.text.isEmpty()) {
return titleResult; return titleResult;
} }
return titleResult + '\n' + descriptionResult;
titleResult.text += '\n';
appendTextWithEntities(titleResult, std_::move(descriptionResult));
return titleResult;
} }
ImagePtr HistoryWebPage::replyPreview() { ImagePtr HistoryWebPage::replyPreview() {
@ -6399,24 +6407,31 @@ TextSelection HistoryLocation::adjustSelection(TextSelection selection, TextSele
} }
QString HistoryLocation::inDialogsText() const { QString HistoryLocation::inDialogsText() const {
return _title.isEmpty() ? lang(lng_maps_point) : _title.original(AllTextSelection); return _title.isEmpty() ? lang(lng_maps_point) : _title.originalText(AllTextSelection);
} }
QString HistoryLocation::selectedText(TextSelection selection) const { TextWithEntities HistoryLocation::selectedText(TextSelection selection) const {
if (selection == FullSelection) { if (selection == FullSelection) {
auto result = qsl("[ ") + lang(lng_maps_point) + qsl(" ]\n"); TextWithEntities result = { qsl("[ ") + lang(lng_maps_point) + qsl(" ]\n"), EntitiesInText() };
auto info = selectedText(AllTextSelection); auto info = selectedText(AllTextSelection);
if (!info.isEmpty()) result.append(info).append('\n'); if (!info.text.isEmpty()) {
return result + _link->dragText(); appendTextWithEntities(result, std_::move(info));
result.text.append('\n');
}
result.text += _link->dragText();
return result;
} }
auto titleResult = _title.original(selection);
auto descriptionResult = _description.original(toDescriptionSelection(selection)); auto titleResult = _title.originalTextWithEntities(selection);
if (titleResult.isEmpty()) { auto descriptionResult = _description.originalTextWithEntities(toDescriptionSelection(selection));
if (titleResult.text.isEmpty()) {
return descriptionResult; return descriptionResult;
} else if (descriptionResult.isEmpty()) { } else if (descriptionResult.text.isEmpty()) {
return titleResult; return titleResult;
} }
return titleResult + '\n' + descriptionResult; titleResult.text += '\n';
appendTextWithEntities(titleResult, std_::move(descriptionResult));
return titleResult;
} }
int32 HistoryLocation::fullWidth() const { int32 HistoryLocation::fullWidth() const {
@ -6729,7 +6744,12 @@ HistoryMessage::HistoryMessage(History *history, const MTPDmessage &msg)
QString text(textClean(qs(msg.vmessage))); QString text(textClean(qs(msg.vmessage)));
initMedia(msg.has_media() ? (&msg.vmedia) : nullptr, text); initMedia(msg.has_media() ? (&msg.vmedia) : nullptr, text);
setText(text, msg.has_entities() ? entitiesFromMTP(msg.ventities.c_vector().v) : EntitiesInText());
TextWithEntities textWithEntities = { text, EntitiesInText() };
if (msg.has_entities()) {
textWithEntities.entities = entitiesFromMTP(msg.ventities.c_vector().v);
}
setText(textWithEntities);
} }
HistoryMessage::HistoryMessage(History *history, MsgId id, MTPDmessage::Flags flags, QDateTime date, int32 from, HistoryMessage *fwd) HistoryMessage::HistoryMessage(History *history, MsgId id, MTPDmessage::Flags flags, QDateTime date, int32 from, HistoryMessage *fwd)
@ -6755,14 +6775,14 @@ HistoryMessage::HistoryMessage(History *history, MsgId id, MTPDmessage::Flags fl
if (HistoryMedia *mediaOriginal = fwd->getMedia()) { if (HistoryMedia *mediaOriginal = fwd->getMedia()) {
_media.reset(mediaOriginal->clone(this)); _media.reset(mediaOriginal->clone(this));
} }
setText(fwd->originalText(), fwd->originalEntities()); setText(fwd->originalText());
} }
HistoryMessage::HistoryMessage(History *history, MsgId id, MTPDmessage::Flags flags, MsgId replyTo, int32 viaBotId, QDateTime date, int32 from, const QString &msg, const EntitiesInText &entities) HistoryMessage::HistoryMessage(History *history, MsgId id, MTPDmessage::Flags flags, MsgId replyTo, int32 viaBotId, QDateTime date, int32 from, const TextWithEntities &textWithEntities)
: HistoryItem(history, id, flags, date, (flags & MTPDmessage::Flag::f_from_id) ? from : 0) { : HistoryItem(history, id, flags, date, (flags & MTPDmessage::Flag::f_from_id) ? from : 0) {
createComponentsHelper(flags, replyTo, viaBotId, MTPnullMarkup); createComponentsHelper(flags, replyTo, viaBotId, MTPnullMarkup);
setText(msg, entities); setText(textWithEntities);
} }
HistoryMessage::HistoryMessage(History *history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, int32 viaBotId, QDateTime date, int32 from, DocumentData *doc, const QString &caption, const MTPReplyMarkup &markup) HistoryMessage::HistoryMessage(History *history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, int32 viaBotId, QDateTime date, int32 from, DocumentData *doc, const QString &caption, const MTPReplyMarkup &markup)
@ -6770,7 +6790,7 @@ HistoryMessage::HistoryMessage(History *history, MsgId msgId, MTPDmessage::Flags
createComponentsHelper(flags, replyTo, viaBotId, markup); createComponentsHelper(flags, replyTo, viaBotId, markup);
initMediaFromDocument(doc, caption); initMediaFromDocument(doc, caption);
setText(QString(), EntitiesInText()); setText(TextWithEntities());
} }
HistoryMessage::HistoryMessage(History *history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, int32 viaBotId, QDateTime date, int32 from, PhotoData *photo, const QString &caption, const MTPReplyMarkup &markup) HistoryMessage::HistoryMessage(History *history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, int32 viaBotId, QDateTime date, int32 from, PhotoData *photo, const QString &caption, const MTPReplyMarkup &markup)
@ -6778,7 +6798,7 @@ HistoryMessage::HistoryMessage(History *history, MsgId msgId, MTPDmessage::Flags
createComponentsHelper(flags, replyTo, viaBotId, markup); createComponentsHelper(flags, replyTo, viaBotId, markup);
_media.reset(new HistoryPhoto(this, photo, caption)); _media.reset(new HistoryPhoto(this, photo, caption));
setText(QString(), EntitiesInText()); setText(TextWithEntities());
} }
void HistoryMessage::createComponentsHelper(MTPDmessage::Flags flags, MsgId replyTo, int32 viaBotId, const MTPReplyMarkup &markup) { void HistoryMessage::createComponentsHelper(MTPDmessage::Flags flags, MsgId replyTo, int32 viaBotId, const MTPReplyMarkup &markup) {
@ -7069,11 +7089,6 @@ void HistoryMessage::applyEdition(const MTPDmessage &message) {
} }
} }
EntitiesInText entities;
if (message.has_entities()) {
entities = entitiesFromMTP(message.ventities.c_vector().v);
}
if (message.has_edit_date()) { if (message.has_edit_date()) {
_flags |= MTPDmessage::Flag::f_edit_date; _flags |= MTPDmessage::Flag::f_edit_date;
if (!Has<HistoryMessageEdited>()) { if (!Has<HistoryMessageEdited>()) {
@ -7083,7 +7098,11 @@ void HistoryMessage::applyEdition(const MTPDmessage &message) {
initTime(); initTime();
} }
setText(qs(message.vmessage), entities); TextWithEntities textWithEntities = { qs(message.vmessage), EntitiesInText() };
if (message.has_entities()) {
textWithEntities.entities = entitiesFromMTP(message.ventities.c_vector().v);
}
setText(textWithEntities);
setMedia(message.has_media() ? (&message.vmedia) : nullptr); setMedia(message.has_media() ? (&message.vmedia) : nullptr);
setReplyMarkup(message.has_reply_markup() ? (&message.vreply_markup) : nullptr); setReplyMarkup(message.has_reply_markup() ? (&message.vreply_markup) : nullptr);
setViewsCount(message.has_views() ? message.vviews.v : -1); setViewsCount(message.has_views() ? message.vviews.v : -1);
@ -7157,36 +7176,44 @@ void HistoryMessage::eraseFromOverview() {
} }
} }
QString HistoryMessage::selectedText(TextSelection selection) const { TextWithEntities HistoryMessage::selectedText(TextSelection selection) const {
QString result, textResult, mediaResult; TextWithEntities result, textResult, mediaResult;
if (selection == FullSelection) { if (selection == FullSelection) {
textResult = _text.original(AllTextSelection, ExpandLinksAll); textResult = _text.originalTextWithEntities(AllTextSelection, ExpandLinksAll);
} else { } else {
textResult = _text.original(selection, ExpandLinksAll); textResult = _text.originalTextWithEntities(selection, ExpandLinksAll);
} }
if (_media) { if (_media) {
mediaResult = _media->selectedText(toMediaSelection(selection)); mediaResult = _media->selectedText(toMediaSelection(selection));
} }
if (textResult.isEmpty()) { if (textResult.text.isEmpty()) {
result = mediaResult; result = mediaResult;
} else if (mediaResult.isEmpty()) { } else if (mediaResult.text.isEmpty()) {
result = textResult; result = textResult;
} else { } else {
result = textResult + qstr("\n\n") + mediaResult; result.text = textResult.text + qstr("\n\n");
result.entities = textResult.entities;
appendTextWithEntities(result, std_::move(mediaResult));
} }
if (auto fwd = Get<HistoryMessageForwarded>()) { if (auto fwd = Get<HistoryMessageForwarded>()) {
if (selection == FullSelection) { if (selection == FullSelection) {
QString fwdinfo = fwd->_text.original(AllTextSelection, ExpandLinksAll), wrapped; auto fwdinfo = fwd->_text.originalTextWithEntities(AllTextSelection, ExpandLinksAll);
wrapped.reserve(fwdinfo.size() + 4 + result.size()); TextWithEntities wrapped;
wrapped.append('[').append(fwdinfo).append(qsl("]\n")).append(result); wrapped.text.reserve(fwdinfo.text.size() + 4 + result.text.size());
wrapped.entities.reserve(fwdinfo.entities.size() + result.entities.size());
wrapped.text.append('[');
appendTextWithEntities(wrapped, std_::move(fwdinfo));
wrapped.text.append(qsl("]\n"));
appendTextWithEntities(wrapped, std_::move(result));
result = wrapped; result = wrapped;
} }
} }
if (auto reply = Get<HistoryMessageReply>()) { if (auto reply = Get<HistoryMessageReply>()) {
if (selection == FullSelection && reply->replyToMsg) { if (selection == FullSelection && reply->replyToMsg) {
QString wrapped; TextWithEntities wrapped;
wrapped.reserve(lang(lng_in_reply_to).size() + reply->replyToMsg->author()->name.size() + 4 + result.size()); wrapped.text.reserve(lang(lng_in_reply_to).size() + reply->replyToMsg->author()->name.size() + 4 + result.text.size());
wrapped.append('[').append(lang(lng_in_reply_to)).append(' ').append(reply->replyToMsg->author()->name).append(qsl("]\n")).append(result); wrapped.text.append('[').append(lang(lng_in_reply_to)).append(' ').append(reply->replyToMsg->author()->name).append(qsl("]\n"));
appendTextWithEntities(wrapped, std_::move(result));
result = wrapped; result = wrapped;
} }
} }
@ -7194,7 +7221,7 @@ QString HistoryMessage::selectedText(TextSelection selection) const {
} }
QString HistoryMessage::inDialogsText() const { QString HistoryMessage::inDialogsText() const {
return emptyText() ? (_media ? _media->inDialogsText() : QString()) : _text.original(AllTextSelection, ExpandLinksNone); return emptyText() ? (_media ? _media->inDialogsText() : QString()) : _text.originalText(AllTextSelection, ExpandLinksNone);
} }
HistoryMedia *HistoryMessage::getMedia() const { HistoryMedia *HistoryMessage::getMedia() const {
@ -7222,16 +7249,16 @@ void HistoryMessage::setMedia(const MTPMessageMedia *media) {
} }
} }
void HistoryMessage::setText(const QString &text, const EntitiesInText &entities) { void HistoryMessage::setText(const TextWithEntities &textWithEntities) {
textstyleSet(&((out() && !isPost()) ? st::outTextStyle : st::inTextStyle)); textstyleSet(&((out() && !isPost()) ? st::outTextStyle : st::inTextStyle));
if (_media && _media->isDisplayed()) { if (_media && _media->isDisplayed()) {
_text.setMarkedText(st::msgFont, text, entities, itemTextOptions(this)); _text.setMarkedText(st::msgFont, textWithEntities, itemTextOptions(this));
} else { } else {
_text.setMarkedText(st::msgFont, text + skipBlock(), entities, itemTextOptions(this)); _text.setMarkedText(st::msgFont, { textWithEntities.text + skipBlock(), textWithEntities.entities }, itemTextOptions(this));
} }
textstyleRestore(); textstyleRestore();
for_const (const auto &entity, entities) { for_const (auto &entity, textWithEntities.entities) {
auto type = entity.type(); auto type = entity.type();
if (type == EntityInTextUrl || type == EntityInTextCustomUrl || type == EntityInTextEmail) { if (type == EntityInTextUrl || type == EntityInTextCustomUrl || type == EntityInTextEmail) {
_flags |= MTPDmessage_ClientFlag::f_has_text_links; _flags |= MTPDmessage_ClientFlag::f_has_text_links;
@ -7286,15 +7313,14 @@ void HistoryMessage::setReplyMarkup(const MTPReplyMarkup *markup) {
} }
} }
QString HistoryMessage::originalText() const { TextWithEntities HistoryMessage::originalText() const {
return emptyText() ? QString() : _text.original(); if (emptyText()) {
return { QString(), EntitiesInText() };
}
return _text.originalTextWithEntities();
} }
EntitiesInText HistoryMessage::originalEntities() const { bool HistoryMessage::textHasLinks() const {
return emptyText() ? EntitiesInText() : _text.originalEntities();
}
bool HistoryMessage::textHasLinks() {
return emptyText() ? false : _text.hasLinks(); return emptyText() ? false : _text.hasLinks();
} }
@ -8114,7 +8140,7 @@ bool HistoryService::updatePinnedText(const QString *pfrom, QString *ptext) {
case MediaTypeVoiceFile: mediaText = lang(lng_action_pinned_media_voice); break; case MediaTypeVoiceFile: mediaText = lang(lng_action_pinned_media_voice); break;
} }
if (mediaText.isEmpty()) { if (mediaText.isEmpty()) {
QString original = pinned->msg->originalText(); QString original = pinned->msg->originalText().text;
int32 cutat = 0, limit = PinnedMessageTextLimit, size = original.size(); int32 cutat = 0, limit = PinnedMessageTextLimit, size = original.size();
for (; limit > 0;) { for (; limit > 0;) {
--limit; --limit;
@ -8192,12 +8218,12 @@ void HistoryService::countPositionAndSize(int32 &left, int32 &width) const {
width = maxwidth - st::msgServiceMargin.left() - st::msgServiceMargin.left(); width = maxwidth - st::msgServiceMargin.left() - st::msgServiceMargin.left();
} }
QString HistoryService::selectedText(TextSelection selection) const { TextWithEntities HistoryService::selectedText(TextSelection selection) const {
return _text.original((selection == FullSelection) ? AllTextSelection : selection); return _text.originalTextWithEntities((selection == FullSelection) ? AllTextSelection : selection);
} }
QString HistoryService::inDialogsText() const { QString HistoryService::inDialogsText() const {
return _text.original(AllTextSelection, ExpandLinksNone); return _text.originalText(AllTextSelection, ExpandLinksNone);
} }
QString HistoryService::inReplyText() const { QString HistoryService::inReplyText() const {
@ -8381,7 +8407,7 @@ void HistoryService::drawInDialog(Painter &p, const QRect &r, bool act, const Hi
} }
QString HistoryService::notificationText() const { QString HistoryService::notificationText() const {
QString msg = _text.original(); QString msg = _text.originalText();
if (msg.size() > 0xFF) msg = msg.mid(0, 0xFF) + qsl("..."); if (msg.size() > 0xFF) msg = msg.mid(0, 0xFF) + qsl("...");
return msg; return msg;
} }

View File

@ -1293,8 +1293,8 @@ public:
} }
virtual void previousItemChanged(); virtual void previousItemChanged();
virtual QString selectedText(TextSelection selection) const { virtual TextWithEntities selectedText(TextSelection selection) const {
return qsl("[-]"); return { qsl("[-]"), EntitiesInText() };
} }
virtual QString inDialogsText() const { virtual QString inDialogsText() const {
return qsl("-"); return qsl("-");
@ -1302,6 +1302,9 @@ public:
virtual QString inReplyText() const { virtual QString inReplyText() const {
return inDialogsText(); return inDialogsText();
} }
virtual TextWithEntities originalText() const {
return { QString(), EntitiesInText() };
}
virtual void drawInfo(Painter &p, int32 right, int32 bottom, int32 width, bool selected, InfoDisplayType type) const { virtual void drawInfo(Painter &p, int32 right, int32 bottom, int32 width, bool selected, InfoDisplayType type) const {
} }
@ -1363,15 +1366,9 @@ public:
virtual HistoryMedia *getMedia() const { virtual HistoryMedia *getMedia() const {
return nullptr; return nullptr;
} }
virtual void setText(const QString &text, const EntitiesInText &links) { virtual void setText(const TextWithEntities &textWithEntities) {
} }
virtual QString originalText() const { virtual bool textHasLinks() const {
return QString();
}
virtual EntitiesInText originalEntities() const {
return EntitiesInText();
}
virtual bool textHasLinks() {
return false; return false;
} }
@ -1663,7 +1660,7 @@ public:
virtual HistoryMediaType type() const = 0; virtual HistoryMediaType type() const = 0;
virtual QString inDialogsText() const = 0; virtual QString inDialogsText() const = 0;
virtual QString selectedText(TextSelection selection) const = 0; virtual TextWithEntities selectedText(TextSelection selection) const = 0;
bool hasPoint(int x, int y) const { bool hasPoint(int x, int y) const {
return (x >= 0 && y >= 0 && x < _width && y < _height); return (x >= 0 && y >= 0 && x < _width && y < _height);
@ -1750,8 +1747,8 @@ public:
virtual ImagePtr replyPreview() { virtual ImagePtr replyPreview() {
return ImagePtr(); return ImagePtr();
} }
virtual QString getCaption() const { virtual TextWithEntities getCaption() const {
return QString(); return TextWithEntities();
} }
virtual bool needsBubble() const = 0; virtual bool needsBubble() const = 0;
virtual bool customInfoLayout() const = 0; virtual bool customInfoLayout() const = 0;
@ -1900,7 +1897,7 @@ public:
} }
QString inDialogsText() const override; QString inDialogsText() const override;
QString selectedText(TextSelection selection) const override; TextWithEntities selectedText(TextSelection selection) const override;
PhotoData *photo() const { PhotoData *photo() const {
return _data; return _data;
@ -1917,8 +1914,8 @@ public:
} }
ImagePtr replyPreview() override; ImagePtr replyPreview() override;
QString getCaption() const override { TextWithEntities getCaption() const override {
return _caption.original(); return _caption.originalTextWithEntities();
} }
bool needsBubble() const override { bool needsBubble() const override {
if (!_caption.isEmpty()) { if (!_caption.isEmpty()) {
@ -1980,7 +1977,7 @@ public:
} }
QString inDialogsText() const override; QString inDialogsText() const override;
QString selectedText(TextSelection selection) const override; TextWithEntities selectedText(TextSelection selection) const override;
DocumentData *getDocument() override { DocumentData *getDocument() override {
return _data; return _data;
@ -2000,8 +1997,8 @@ public:
} }
ImagePtr replyPreview() override; ImagePtr replyPreview() override;
QString getCaption() const override { TextWithEntities getCaption() const override {
return _caption.original(); return _caption.originalTextWithEntities();
} }
bool needsBubble() const override { bool needsBubble() const override {
if (!_caption.isEmpty()) { if (!_caption.isEmpty()) {
@ -2103,7 +2100,7 @@ public:
} }
QString inDialogsText() const override; QString inDialogsText() const override;
QString selectedText(TextSelection selection) const override; TextWithEntities selectedText(TextSelection selection) const override;
bool uploading() const override { bool uploading() const override {
return _data->uploading(); return _data->uploading();
@ -2124,11 +2121,11 @@ public:
} }
ImagePtr replyPreview() override; ImagePtr replyPreview() override;
QString getCaption() const override { TextWithEntities getCaption() const override {
if (const HistoryDocumentCaptioned *captioned = Get<HistoryDocumentCaptioned>()) { if (const HistoryDocumentCaptioned *captioned = Get<HistoryDocumentCaptioned>()) {
return captioned->_caption.original(); return captioned->_caption.originalTextWithEntities();
} }
return QString(); return TextWithEntities();
} }
bool needsBubble() const override { bool needsBubble() const override {
return true; return true;
@ -2190,7 +2187,7 @@ public:
} }
QString inDialogsText() const override; QString inDialogsText() const override;
QString selectedText(TextSelection selection) const override; TextWithEntities selectedText(TextSelection selection) const override;
bool uploading() const override { bool uploading() const override {
return _data->uploading(); return _data->uploading();
@ -2217,8 +2214,8 @@ public:
} }
ImagePtr replyPreview() override; ImagePtr replyPreview() override;
QString getCaption() const override { TextWithEntities getCaption() const override {
return _caption.original(); return _caption.originalTextWithEntities();
} }
bool needsBubble() const override { bool needsBubble() const override {
if (!_caption.isEmpty()) { if (!_caption.isEmpty()) {
@ -2288,7 +2285,7 @@ public:
} }
QString inDialogsText() const override; QString inDialogsText() const override;
QString selectedText(TextSelection selection) const override; TextWithEntities selectedText(TextSelection selection) const override;
DocumentData *getDocument() override { DocumentData *getDocument() override {
return _data; return _data;
@ -2357,7 +2354,7 @@ public:
} }
QString inDialogsText() const override; QString inDialogsText() const override;
QString selectedText(TextSelection selection) const override; TextWithEntities selectedText(TextSelection selection) const override;
void attachToParent() override; void attachToParent() override;
void detachFromParent() override; void detachFromParent() override;
@ -2425,7 +2422,7 @@ public:
} }
QString inDialogsText() const override; QString inDialogsText() const override;
QString selectedText(TextSelection selection) const override; TextWithEntities selectedText(TextSelection selection) const override;
void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override; void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override;
void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override; void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override;
@ -2559,7 +2556,7 @@ public:
} }
QString inDialogsText() const override; QString inDialogsText() const override;
QString selectedText(TextSelection selection) const override; TextWithEntities selectedText(TextSelection selection) const override;
bool needsBubble() const override { bool needsBubble() const override {
if (!_title.isEmpty() || !_description.isEmpty()) { if (!_title.isEmpty() || !_description.isEmpty()) {
@ -2613,8 +2610,8 @@ public:
static HistoryMessage *create(History *history, MsgId msgId, MTPDmessage::Flags flags, QDateTime date, int32 from, HistoryMessage *fwd) { static HistoryMessage *create(History *history, MsgId msgId, MTPDmessage::Flags flags, QDateTime date, int32 from, HistoryMessage *fwd) {
return _create(history, msgId, flags, date, from, fwd); return _create(history, msgId, flags, date, from, fwd);
} }
static HistoryMessage *create(History *history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, int32 viaBotId, QDateTime date, int32 from, const QString &msg, const EntitiesInText &entities) { static HistoryMessage *create(History *history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, int32 viaBotId, QDateTime date, int32 from, const TextWithEntities &textWithEntities) {
return _create(history, msgId, flags, replyTo, viaBotId, date, from, msg, entities); return _create(history, msgId, flags, replyTo, viaBotId, date, from, textWithEntities);
} }
static HistoryMessage *create(History *history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, int32 viaBotId, QDateTime date, int32 from, DocumentData *doc, const QString &caption, const MTPReplyMarkup &markup) { static HistoryMessage *create(History *history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, int32 viaBotId, QDateTime date, int32 from, DocumentData *doc, const QString &caption, const MTPReplyMarkup &markup) {
return _create(history, msgId, flags, replyTo, viaBotId, date, from, doc, caption, markup); return _create(history, msgId, flags, replyTo, viaBotId, date, from, doc, caption, markup);
@ -2685,13 +2682,12 @@ public:
int32 addToOverview(AddToOverviewMethod method) override; int32 addToOverview(AddToOverviewMethod method) override;
void eraseFromOverview(); void eraseFromOverview();
QString selectedText(TextSelection selection) const override; TextWithEntities selectedText(TextSelection selection) const override;
QString inDialogsText() const override; QString inDialogsText() const override;
HistoryMedia *getMedia() const override; HistoryMedia *getMedia() const override;
void setText(const QString &text, const EntitiesInText &entities) override; void setText(const TextWithEntities &textWithEntities) override;
QString originalText() const override; TextWithEntities originalText() const override;
EntitiesInText originalEntities() const override; bool textHasLinks() const override;
bool textHasLinks() override;
int32 infoWidth() const override { int32 infoWidth() const override {
int32 result = _timeWidth; int32 result = _timeWidth;
@ -2753,7 +2749,7 @@ private:
HistoryMessage(History *history, const MTPDmessage &msg); HistoryMessage(History *history, const MTPDmessage &msg);
HistoryMessage(History *history, MsgId msgId, MTPDmessage::Flags flags, QDateTime date, int32 from, HistoryMessage *fwd); // local forwarded HistoryMessage(History *history, MsgId msgId, MTPDmessage::Flags flags, QDateTime date, int32 from, HistoryMessage *fwd); // local forwarded
HistoryMessage(History *history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, int32 viaBotId, QDateTime date, int32 from, const QString &msg, const EntitiesInText &entities); // local message HistoryMessage(History *history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, int32 viaBotId, QDateTime date, int32 from, const TextWithEntities &textWithEntities); // local message
HistoryMessage(History *history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, int32 viaBotId, QDateTime date, int32 from, DocumentData *doc, const QString &caption, const MTPReplyMarkup &markup); // local document HistoryMessage(History *history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, int32 viaBotId, QDateTime date, int32 from, DocumentData *doc, const QString &caption, const MTPReplyMarkup &markup); // local document
HistoryMessage(History *history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, int32 viaBotId, QDateTime date, int32 from, PhotoData *photo, const QString &caption, const MTPReplyMarkup &markup); // local photo HistoryMessage(History *history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, int32 viaBotId, QDateTime date, int32 from, PhotoData *photo, const QString &caption, const MTPReplyMarkup &markup); // local photo
friend class HistoryItemInstantiated<HistoryMessage>; friend class HistoryItemInstantiated<HistoryMessage>;
@ -2902,7 +2898,7 @@ public:
bool serviceMsg() const override { bool serviceMsg() const override {
return true; return true;
} }
QString selectedText(TextSelection selection) const override; TextWithEntities selectedText(TextSelection selection) const override;
QString inDialogsText() const override; QString inDialogsText() const override;
QString inReplyText() const override; QString inReplyText() const override;
@ -2939,8 +2935,8 @@ public:
HistoryTextState getState(int x, int y, HistoryStateRequest request) const override; HistoryTextState getState(int x, int y, HistoryStateRequest request) const override;
QString selectedText(TextSelection selection) const override { TextWithEntities selectedText(TextSelection selection) const override {
return QString(); return { QString(), EntitiesInText() };
} }
HistoryItemType type() const override { HistoryItemType type() const override {
return HistoryItemGroup; return HistoryItemGroup;
@ -2989,8 +2985,8 @@ public:
void draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const override; void draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const override;
HistoryTextState getState(int x, int y, HistoryStateRequest request) const override; HistoryTextState getState(int x, int y, HistoryStateRequest request) const override;
QString selectedText(TextSelection selection) const override { TextWithEntities selectedText(TextSelection selection) const override {
return QString(); return { QString(), EntitiesInText() };
} }
HistoryItemType type() const override { HistoryItemType type() const override {
return HistoryItemCollapse; return HistoryItemCollapse;

View File

@ -39,6 +39,54 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
#include "window/top_bar_widget.h" #include "window/top_bar_widget.h"
#include "playerwidget.h" #include "playerwidget.h"
namespace {
QString mimeTagFromTag(const QString &tagId) {
if (tagId.startsWith(qstr("mention://"))) {
return tagId + ':' + QString::number(MTP::authedId());
}
return tagId;
}
QMimeData *mimeDataFromTextWithEntities(const TextWithEntities &forClipboard) {
if (forClipboard.text.isEmpty()) {
return nullptr;
}
auto result = new QMimeData();
result->setText(forClipboard.text);
auto tags = textTagsFromEntities(forClipboard.entities);
if (!tags.isEmpty()) {
for (auto &tag : tags) {
tag.id = mimeTagFromTag(tag.id);
}
result->setData(FlatTextarea::tagsMimeType(), FlatTextarea::serializeTagsList(tags));
}
return result;
}
// For mention tags save and validate userId, ignore tags for different userId.
class FieldTagMimeProcessor : public FlatTextarea::TagMimeProcessor {
public:
QString mimeTagFromTag(const QString &tagId) override {
return ::mimeTagFromTag(tagId);
}
QString tagFromMimeTag(const QString &mimeTag) override {
if (mimeTag.startsWith(qstr("mention://"))) {
auto match = QRegularExpression(":(\\d+)$").match(mimeTag);
if (!match.hasMatch() || match.capturedRef(1).toInt() != MTP::authedId()) {
return QString();
}
return mimeTag.mid(0, mimeTag.size() - match.capturedLength());
}
return mimeTag;
}
};
} // namespace
// flick scroll taken from http://qt-project.org/doc/qt-4.8/demos-embedded-anomaly-src-flickcharm-cpp.html // flick scroll taken from http://qt-project.org/doc/qt-4.8/demos-embedded-anomaly-src-flickcharm-cpp.html
HistoryInner::HistoryInner(HistoryWidget *historyWidget, ScrollArea *scroll, History *history) : TWidget(nullptr) HistoryInner::HistoryInner(HistoryWidget *historyWidget, ScrollArea *scroll, History *history) : TWidget(nullptr)
@ -704,24 +752,21 @@ void HistoryInner::onDragExec() {
} }
} }
ClickHandlerPtr pressedHandler = ClickHandler::getPressed(); ClickHandlerPtr pressedHandler = ClickHandler::getPressed();
QString sel; TextWithEntities sel;
QList<QUrl> urls; QList<QUrl> urls;
if (uponSelected) { if (uponSelected) {
sel = getSelectedText(); sel = getSelectedText();
} else if (pressedHandler) { } else if (pressedHandler) {
sel = pressedHandler->dragText(); sel = { pressedHandler->dragText(), EntitiesInText() };
//if (!sel.isEmpty() && sel.at(0) != '/' && sel.at(0) != '@' && sel.at(0) != '#') { //if (!sel.isEmpty() && sel.at(0) != '/' && sel.at(0) != '@' && sel.at(0) != '#') {
// urls.push_back(QUrl::fromEncoded(sel.toUtf8())); // Google Chrome crashes in Mac OS X O_o // urls.push_back(QUrl::fromEncoded(sel.toUtf8())); // Google Chrome crashes in Mac OS X O_o
//} //}
} }
if (!sel.isEmpty()) { if (auto mimeData = mimeDataFromTextWithEntities(sel)) {
updateDragSelection(0, 0, false); updateDragSelection(0, 0, false);
_widget->noSelectingScroll(); _widget->noSelectingScroll();
QDrag *drag = new QDrag(App::wnd()); QDrag *drag = new QDrag(App::wnd());
QMimeData *mimeData = new QMimeData;
mimeData->setText(sel);
if (!urls.isEmpty()) mimeData->setUrls(urls); if (!urls.isEmpty()) mimeData->setUrls(urls);
if (uponSelected && !_selected.isEmpty() && _selected.cbegin().value() == FullSelection && !Adaptive::OneColumn()) { if (uponSelected && !_selected.isEmpty() && _selected.cbegin().value() == FullSelection && !Adaptive::OneColumn()) {
mimeData->setData(qsl("application/x-td-forward-selected"), "1"); mimeData->setData(qsl("application/x-td-forward-selected"), "1");
@ -748,7 +793,7 @@ void HistoryInner::onDragExec() {
} }
if (!forwardMimeType.isEmpty()) { if (!forwardMimeType.isEmpty()) {
QDrag *drag = new QDrag(App::wnd()); QDrag *drag = new QDrag(App::wnd());
QMimeData *mimeData = new QMimeData; QMimeData *mimeData = new QMimeData();
mimeData->setData(forwardMimeType, "1"); mimeData->setData(forwardMimeType, "1");
if (DocumentData *document = (pressedMedia ? pressedMedia->getDocument() : nullptr)) { if (DocumentData *document = (pressedMedia ? pressedMedia->getDocument() : nullptr)) {
@ -1124,10 +1169,7 @@ void HistoryInner::onMenuDestroy(QObject *obj) {
} }
void HistoryInner::copySelectedText() { void HistoryInner::copySelectedText() {
QString sel = getSelectedText(); setToClipboard(getSelectedText());
if (!sel.isEmpty()) {
QApplication::clipboard()->setText(sel);
}
} }
void HistoryInner::copyContextUrl() { void HistoryInner::copyContextUrl() {
@ -1217,9 +1259,12 @@ void HistoryInner::copyContextText() {
return; return;
} }
QString contextMenuText = item->selectedText(FullSelection); setToClipboard(item->selectedText(FullSelection));
if (!contextMenuText.isEmpty()) { }
QApplication::clipboard()->setText(contextMenuText);
void HistoryInner::setToClipboard(const TextWithEntities &forClipboard) {
if (auto data = mimeDataFromTextWithEntities(forClipboard)) {
QApplication::clipboard()->setMimeData(data);
} }
} }
@ -1227,42 +1272,48 @@ void HistoryInner::resizeEvent(QResizeEvent *e) {
onUpdateSelected(); onUpdateSelected();
} }
QString HistoryInner::getSelectedText() const { TextWithEntities HistoryInner::getSelectedText() const {
SelectedItems sel = _selected; SelectedItems sel = _selected;
if (_dragAction == Selecting && _dragSelFrom && _dragSelTo) { if (_dragAction == Selecting && _dragSelFrom && _dragSelTo) {
applyDragSelection(&sel); applyDragSelection(&sel);
} }
if (sel.isEmpty()) return QString(); if (sel.isEmpty()) {
return TextWithEntities();
}
if (sel.cbegin().value() != FullSelection) { if (sel.cbegin().value() != FullSelection) {
return sel.cbegin().key()->selectedText(sel.cbegin().value()); return sel.cbegin().key()->selectedText(sel.cbegin().value());
} }
int32 fullSize = 0, mtop = migratedTop(), htop = historyTop(); int fullSize = 0;
QString timeFormat(qsl(", [dd.MM.yy hh:mm]\n")); QString timeFormat(qsl(", [dd.MM.yy hh:mm]\n"));
QMap<int32, QString> texts; QMap<int, TextWithEntities> texts;
for (SelectedItems::const_iterator i = sel.cbegin(), e = sel.cend(); i != e; ++i) { for (auto i = sel.cbegin(), e = sel.cend(); i != e; ++i) {
HistoryItem *item = i.key(); HistoryItem *item = i.key();
if (item->detached()) continue; if (item->detached()) continue;
QString text, sel = item->selectedText(FullSelection), time = item->date.toString(timeFormat); QString time = item->date.toString(timeFormat);
int32 size = item->author()->name.size() + time.size() + sel.size(); TextWithEntities part, unwrapped = item->selectedText(FullSelection);
text.reserve(size); int size = item->author()->name.size() + time.size() + unwrapped.text.size();
part.text.reserve(size);
int32 y = itemTop(item); int y = itemTop(item);
if (y >= 0) { if (y >= 0) {
texts.insert(y, text.append(item->author()->name).append(time).append(sel)); part.text.append(item->author()->name).append(time);
appendTextWithEntities(part, std_::move(unwrapped));
texts.insert(y, part);
fullSize += size; fullSize += size;
} }
} }
QString result, sep(qsl("\n\n")); TextWithEntities result;
result.reserve(fullSize + (texts.size() - 1) * 2); auto sep = qsl("\n\n");
for (QMap<int32, QString>::const_iterator i = texts.cbegin(), e = texts.cend(); i != e; ++i) { result.text.reserve(fullSize + (texts.size() - 1) * sep.size());
result.append(i.value()); for (auto i = texts.begin(), e = texts.end(); i != e; ++i) {
appendTextWithEntities(result, std_::move(i.value()));
if (i + 1 != e) { if (i + 1 != e) {
result.append(sep); result.text.append(sep);
} }
} }
return result; return result;
@ -2027,7 +2078,7 @@ QString HistoryInner::tooltipText() const {
} else if (_dragCursorState == HistoryInForwardedCursorState && _dragAction == NoDrag) { } else if (_dragCursorState == HistoryInForwardedCursorState && _dragAction == NoDrag) {
if (App::hoveredItem()) { if (App::hoveredItem()) {
if (HistoryMessageForwarded *fwd = App::hoveredItem()->Get<HistoryMessageForwarded>()) { if (HistoryMessageForwarded *fwd = App::hoveredItem()->Get<HistoryMessageForwarded>()) {
return fwd->_text.original(AllTextSelection, ExpandLinksNone); return fwd->_text.originalText(AllTextSelection, ExpandLinksNone);
} }
} }
} else if (ClickHandlerPtr lnk = ClickHandler::getActive()) { } else if (ClickHandlerPtr lnk = ClickHandler::getActive()) {
@ -2679,7 +2730,7 @@ bool HistoryHider::offerPeer(PeerId peer) {
} }
QString HistoryHider::offeredText() const { QString HistoryHider::offeredText() const {
return toText.original(); return toText.originalText();
} }
bool HistoryHider::wasOffered() const { bool HistoryHider::wasOffered() const {
@ -2772,32 +2823,6 @@ TextWithTags::Tags textTagsFromEntities(const EntitiesInText &entities) {
return result; return result;
} }
namespace {
// For mention tags save and validate userId, ignore tags for different userId.
class FieldTagMimeProcessor : public FlatTextarea::TagMimeProcessor {
public:
QString mimeTagFromTag(const QString &tagId) override {
if (tagId.startsWith(qstr("mention://"))) {
return tagId + ':' + QString::number(MTP::authedId());
}
return tagId;
}
QString tagFromMimeTag(const QString &mimeTag) override {
if (mimeTag.startsWith(qstr("mention://"))) {
auto match = QRegularExpression(":(\\d+)$").match(mimeTag);
if (!match.hasMatch() || match.capturedRef(1).toInt() != MTP::authedId()) {
return QString();
}
return mimeTag.mid(0, mimeTag.size() - match.capturedLength());
}
return mimeTag;
}
};
} // namespace
HistoryWidget::HistoryWidget(QWidget *parent) : TWidget(parent) HistoryWidget::HistoryWidget(QWidget *parent) : TWidget(parent)
, _fieldBarCancel(this, st::replyCancel) , _fieldBarCancel(this, st::replyCancel)
, _scroll(this, st::historyScroll, false) , _scroll(this, st::historyScroll, false)
@ -6258,8 +6283,8 @@ void HistoryWidget::onPhotoUploaded(const FullMsgId &newId, bool silent, const M
if (silentPost) { if (silentPost) {
sendFlags |= MTPmessages_SendMedia::Flag::f_silent; sendFlags |= MTPmessages_SendMedia::Flag::f_silent;
} }
QString caption = item->getMedia() ? item->getMedia()->getCaption() : QString(); auto caption = item->getMedia() ? item->getMedia()->getCaption() : TextWithEntities();
hist->sendRequestId = MTP::send(MTPmessages_SendMedia(MTP_flags(sendFlags), item->history()->peer->input, MTP_int(replyTo), MTP_inputMediaUploadedPhoto(file, MTP_string(caption)), MTP_long(randomId), MTPnullMarkup), App::main()->rpcDone(&MainWidget::sentUpdatesReceived), App::main()->rpcFail(&MainWidget::sendMessageFail), 0, 0, hist->sendRequestId); hist->sendRequestId = MTP::send(MTPmessages_SendMedia(MTP_flags(sendFlags), item->history()->peer->input, MTP_int(replyTo), MTP_inputMediaUploadedPhoto(file, MTP_string(caption.text)), MTP_long(randomId), MTPnullMarkup), App::main()->rpcDone(&MainWidget::sentUpdatesReceived), App::main()->rpcFail(&MainWidget::sendMessageFail), 0, 0, hist->sendRequestId);
} }
} }
@ -6310,8 +6335,8 @@ void HistoryWidget::onDocumentUploaded(const FullMsgId &newId, bool silent, cons
if (silentPost) { if (silentPost) {
sendFlags |= MTPmessages_SendMedia::Flag::f_silent; sendFlags |= MTPmessages_SendMedia::Flag::f_silent;
} }
QString caption = item->getMedia() ? item->getMedia()->getCaption() : QString(); auto caption = item->getMedia() ? item->getMedia()->getCaption() : TextWithEntities();
hist->sendRequestId = MTP::send(MTPmessages_SendMedia(MTP_flags(sendFlags), item->history()->peer->input, MTP_int(replyTo), MTP_inputMediaUploadedDocument(file, MTP_string(document->mime), _composeDocumentAttributes(document), MTP_string(caption)), MTP_long(randomId), MTPnullMarkup), App::main()->rpcDone(&MainWidget::sentUpdatesReceived), App::main()->rpcFail(&MainWidget::sendMessageFail), 0, 0, hist->sendRequestId); hist->sendRequestId = MTP::send(MTPmessages_SendMedia(MTP_flags(sendFlags), item->history()->peer->input, MTP_int(replyTo), MTP_inputMediaUploadedDocument(file, MTP_string(document->mime), _composeDocumentAttributes(document), MTP_string(caption.text)), MTP_long(randomId), MTPnullMarkup), App::main()->rpcDone(&MainWidget::sentUpdatesReceived), App::main()->rpcFail(&MainWidget::sendMessageFail), 0, 0, hist->sendRequestId);
} }
} }
} }
@ -6339,8 +6364,8 @@ void HistoryWidget::onThumbDocumentUploaded(const FullMsgId &newId, bool silent,
if (silentPost) { if (silentPost) {
sendFlags |= MTPmessages_SendMedia::Flag::f_silent; sendFlags |= MTPmessages_SendMedia::Flag::f_silent;
} }
QString caption = item->getMedia() ? item->getMedia()->getCaption() : QString(); auto caption = item->getMedia() ? item->getMedia()->getCaption() : TextWithEntities();
hist->sendRequestId = MTP::send(MTPmessages_SendMedia(MTP_flags(sendFlags), item->history()->peer->input, MTP_int(replyTo), MTP_inputMediaUploadedThumbDocument(file, thumb, MTP_string(document->mime), _composeDocumentAttributes(document), MTP_string(caption)), MTP_long(randomId), MTPnullMarkup), App::main()->rpcDone(&MainWidget::sentUpdatesReceived), App::main()->rpcFail(&MainWidget::sendMessageFail), 0, 0, hist->sendRequestId); hist->sendRequestId = MTP::send(MTPmessages_SendMedia(MTP_flags(sendFlags), item->history()->peer->input, MTP_int(replyTo), MTP_inputMediaUploadedThumbDocument(file, thumb, MTP_string(document->mime), _composeDocumentAttributes(document), MTP_string(caption.text)), MTP_long(randomId), MTPnullMarkup), App::main()->rpcDone(&MainWidget::sentUpdatesReceived), App::main()->rpcFail(&MainWidget::sendMessageFail), 0, 0, hist->sendRequestId);
} }
} }
} }
@ -7414,11 +7439,12 @@ void HistoryWidget::onEditMessage() {
_history->clearMsgDraft(); _history->clearMsgDraft();
} }
auto originalText = to->originalText(); auto original = to->originalText();
auto originalEntities = to->originalEntities(); auto editText = textApplyEntities(original.text, original.entities);
TextWithTags original = { textApplyEntities(originalText, originalEntities), textTagsFromEntities(originalEntities) }; auto editTags = textTagsFromEntities(original.entities);
MessageCursor cursor = { original.text.size(), original.text.size(), QFIXED_MAX }; TextWithTags editData = { editText, editTags };
_history->setEditDraft(std_::make_unique<HistoryEditDraft>(original, to->id, cursor, false)); MessageCursor cursor = { editText.size(), editText.size(), QFIXED_MAX };
_history->setEditDraft(std_::make_unique<HistoryEditDraft>(editData, to->id, cursor, false));
applyDraft(false); applyDraft(false);
_previewData = 0; _previewData = 0;
@ -7755,10 +7781,11 @@ void HistoryWidget::onCancel() {
if (_inlineBotCancel) { if (_inlineBotCancel) {
onInlineBotCancel(); onInlineBotCancel();
} else if (_editMsgId) { } else if (_editMsgId) {
auto originalText = _replyEditMsg ? _replyEditMsg->originalText() : QString(); auto original = _replyEditMsg ? _replyEditMsg->originalText() : TextWithEntities();
auto originalEntities = _replyEditMsg ? _replyEditMsg->originalEntities() : EntitiesInText(); auto editText = textApplyEntities(original.text, original.entities);
TextWithTags original = { textApplyEntities(originalText, originalEntities), textTagsFromEntities(originalEntities) }; auto editTags = textTagsFromEntities(original.entities);
if (_replyEditMsg && original != _field.getTextWithTags()) { TextWithTags editData = { editText, editTags };
if (_replyEditMsg && editData != _field.getTextWithTags()) {
auto box = new ConfirmBox(lang(lng_cancel_edit_post_sure), lang(lng_cancel_edit_post_yes), st::defaultBoxButton, lang(lng_cancel_edit_post_no)); auto box = new ConfirmBox(lang(lng_cancel_edit_post_sure), lang(lng_cancel_edit_post_yes), st::defaultBoxButton, lang(lng_cancel_edit_post_no));
connect(box, SIGNAL(confirmed()), this, SLOT(onFieldBarCancel())); connect(box, SIGNAL(confirmed()), this, SLOT(onFieldBarCancel()));
Ui::showLayer(box); Ui::showLayer(box);

View File

@ -57,7 +57,7 @@ public:
void keyPressEvent(QKeyEvent *e) override; void keyPressEvent(QKeyEvent *e) override;
void showContextMenu(QContextMenuEvent *e, bool showFromTouch = false); void showContextMenu(QContextMenuEvent *e, bool showFromTouch = false);
QString getSelectedText() const; TextWithEntities getSelectedText() const;
void dragActionStart(const QPoint &screenPos, Qt::MouseButton button = Qt::LeftButton); void dragActionStart(const QPoint &screenPos, Qt::MouseButton button = Qt::LeftButton);
void dragActionUpdate(const QPoint &screenPos); void dragActionUpdate(const QPoint &screenPos);
@ -144,6 +144,8 @@ private:
HistoryItem *nextItem(HistoryItem *item); HistoryItem *nextItem(HistoryItem *item);
void updateDragSelection(HistoryItem *dragSelFrom, HistoryItem *dragSelTo, bool dragSelecting, bool force = false); void updateDragSelection(HistoryItem *dragSelFrom, HistoryItem *dragSelTo, bool dragSelecting, bool force = false);
void setToClipboard(const TextWithEntities &forClipboard);
PeerData *_peer = nullptr; PeerData *_peer = nullptr;
History *_migrated = nullptr; History *_migrated = nullptr;
History *_history = nullptr; History *_history = nullptr;

View File

@ -3947,8 +3947,9 @@ void MainWidget::feedUpdates(const MTPUpdates &updates, uint64 randomId) {
feedUpdate(MTP_updateMessageID(d.vid, MTP_long(randomId))); // ignore real date feedUpdate(MTP_updateMessageID(d.vid, MTP_long(randomId))); // ignore real date
if (peerId) { if (peerId) {
if (HistoryItem *item = App::histItemById(peerToChannel(peerId), d.vid.v)) { if (auto item = App::histItemById(peerToChannel(peerId), d.vid.v)) {
item->setText(text, d.has_entities() ? entitiesFromMTP(d.ventities.c_vector().v) : EntitiesInText()); auto entities = d.has_entities() ? entitiesFromMTP(d.ventities.c_vector().v) : EntitiesInText();
item->setText({ text, entities });
item->updateMedia(d.has_media() ? (&d.vmedia) : nullptr); item->updateMedia(d.has_media() ? (&d.vmedia) : nullptr);
item->addToOverview(AddToOverviewNew); item->addToOverview(AddToOverviewNew);
} }

View File

@ -912,7 +912,7 @@ void MediaView::displayPhoto(PhotoData *photo, HistoryItem *item) {
_caption = Text(); _caption = Text();
if (HistoryMessage *itemMsg = item ? item->toHistoryMessage() : nullptr) { if (HistoryMessage *itemMsg = item ? item->toHistoryMessage() : nullptr) {
if (HistoryPhoto *photoMsg = dynamic_cast<HistoryPhoto*>(itemMsg->getMedia())) { if (HistoryPhoto *photoMsg = dynamic_cast<HistoryPhoto*>(itemMsg->getMedia())) {
_caption.setText(st::mvCaptionFont, photoMsg->getCaption(), (item->author()->isUser() && item->author()->asUser()->botInfo) ? _captionBotOptions : _captionTextOptions); _caption.setMarkedText(st::mvCaptionFont, photoMsg->getCaption(), (item->author()->isUser() && item->author()->asUser()->botInfo) ? _captionBotOptions : _captionTextOptions);
} }
} }

View File

@ -898,9 +898,11 @@ bool Document::updateStatusText() const {
Link::Link(HistoryMedia *media, HistoryItem *parent) : ItemBase(parent) { Link::Link(HistoryMedia *media, HistoryItem *parent) : ItemBase(parent) {
AddComponents(Info::Bit()); AddComponents(Info::Bit());
QString text = _parent->originalText(), mainUrl; const auto textWithEntities = _parent->originalText();
EntitiesInText entities = _parent->originalEntities(); QString mainUrl;
auto text = textWithEntities.text;
auto &entities = textWithEntities.entities;
int32 from = 0, till = text.size(), lnk = entities.size(); int32 from = 0, till = text.size(), lnk = entities.size();
for_const (const auto &entity, entities) { for_const (const auto &entity, entities) {
auto type = entity.type(); auto type = entity.type();

View File

@ -1011,7 +1011,8 @@ void InputArea::processDocumentContentsChange(int position, int charsAdded) {
if (!_inner.document()->pageSize().isNull()) { if (!_inner.document()->pageSize().isNull()) {
_inner.document()->setPageSize(QSizeF(0, 0)); _inner.document()->setPageSize(QSizeF(0, 0));
} }
QTextCursor c(doc->docHandle(), replacePosition); QTextCursor c(doc->docHandle(), 0);
c.setPosition(replacePosition);
c.setPosition(replacePosition + replaceLen, QTextCursor::KeepAnchor); c.setPosition(replacePosition + replaceLen, QTextCursor::KeepAnchor);
if (emoji) { if (emoji) {
insertEmoji(emoji, c); insertEmoji(emoji, c);

View File

@ -463,7 +463,7 @@ void FlatTextarea::insertTag(const QString &text, QString tagId) {
if (previousChar == '@' || previousChar == '#' || previousChar == '/') { if (previousChar == '@' || previousChar == '#' || previousChar == '/') {
if ((i == pos - fragmentPosition || (previousChar == '/' ? fragmentText.at(i).isLetterOrNumber() : fragmentText.at(i).isLetter()) || previousChar == '#') && if ((i == pos - fragmentPosition || (previousChar == '/' ? fragmentText.at(i).isLetterOrNumber() : fragmentText.at(i).isLetter()) || previousChar == '#') &&
(i < 2 || !(fragmentText.at(i - 2).isLetterOrNumber() || fragmentText.at(i - 2) == '_'))) { (i < 2 || !(fragmentText.at(i - 2).isLetterOrNumber() || fragmentText.at(i - 2) == '_'))) {
cursor.setPosition(fragmentPosition + i - 1, QTextCursor::MoveAnchor); cursor.setPosition(fragmentPosition + i - 1);
int till = fragmentPosition + i; int till = fragmentPosition + i;
for (; (till < fragmentEnd) && (till - fragmentPosition - i + 1 < text.size()); ++till) { for (; (till < fragmentEnd) && (till - fragmentPosition - i + 1 < text.size()); ++till) {
if (fragmentText.at(till - fragmentPosition).toLower() != text.at(till - fragmentPosition - i + 1).toLower()) { if (fragmentText.at(till - fragmentPosition).toLower() != text.at(till - fragmentPosition - i + 1).toLower()) {
@ -608,6 +608,13 @@ public:
_currentStart = currentPosition; _currentStart = currentPosition;
}; };
void finish() {
if (_currentTag < _tags->size()) {
_tags->resize(_currentTag);
_changed = true;
}
}
private: private:
FlatTextarea::TagList *_tags; FlatTextarea::TagList *_tags;
bool _changed = false; bool _changed = false;
@ -709,6 +716,8 @@ QString FlatTextarea::getTextPart(int start, int end, TagList *outTagsList, bool
result.chop(1); result.chop(1);
if (tillFragmentEnd) tagAccumulator.feed(QString(), result.size()); if (tillFragmentEnd) tagAccumulator.feed(QString(), result.size());
tagAccumulator.finish();
if (outTagsChanged) { if (outTagsChanged) {
*outTagsChanged = tagAccumulator.changed(); *outTagsChanged = tagAccumulator.changed();
} }
@ -842,7 +851,8 @@ void FlatTextarea::insertFromMimeData(const QMimeData *source) {
} else { } else {
_insertedTags.clear(); _insertedTags.clear();
} }
_realInsertPosition = textCursor().position(); auto cursor = textCursor();
_realInsertPosition = qMin(cursor.position(), cursor.anchor());
_realCharsAdded = text.size(); _realCharsAdded = text.size();
QTextEdit::insertFromMimeData(source); QTextEdit::insertFromMimeData(source);
if (!_inDrop) { if (!_inDrop) {
@ -895,7 +905,8 @@ void prepareFormattingOptimization(QTextDocument *document) {
} }
void removeTags(QTextDocument *document, int from, int end) { void removeTags(QTextDocument *document, int from, int end) {
QTextCursor c(document->docHandle(), from); QTextCursor c(document->docHandle(), 0);
c.setPosition(from);
c.setPosition(end, QTextCursor::KeepAnchor); c.setPosition(end, QTextCursor::KeepAnchor);
QTextCharFormat format; QTextCharFormat format;
@ -923,7 +934,8 @@ int processInsertedTags(QTextDocument *document, int changedPosition, int change
if (applyNoTagFrom < tagFrom) { if (applyNoTagFrom < tagFrom) {
removeTags(document, applyNoTagFrom, tagFrom); removeTags(document, applyNoTagFrom, tagFrom);
} }
QTextCursor c(document->docHandle(), tagFrom); QTextCursor c(document->docHandle(), 0);
c.setPosition(tagFrom);
c.setPosition(tagTo, QTextCursor::KeepAnchor); c.setPosition(tagTo, QTextCursor::KeepAnchor);
QTextCharFormat format; QTextCharFormat format;
@ -1098,7 +1110,8 @@ void FlatTextarea::processFormatting(int insertPosition, int insertEnd) {
if (action.type != ActionType::Invalid) { if (action.type != ActionType::Invalid) {
prepareFormattingOptimization(doc); prepareFormattingOptimization(doc);
QTextCursor c(doc->docHandle(), action.intervalStart); QTextCursor c(doc->docHandle(), 0);
c.setPosition(action.intervalStart);
c.setPosition(action.intervalEnd, QTextCursor::KeepAnchor); c.setPosition(action.intervalEnd, QTextCursor::KeepAnchor);
if (action.type == ActionType::InsertEmoji) { if (action.type == ActionType::InsertEmoji) {
insertEmoji(action.emoji, c); insertEmoji(action.emoji, c);

View File

@ -555,14 +555,15 @@ public:
} }
parse(options); parse(options);
} }
TextParser(Text *t, const QString &text, const EntitiesInText &preparsed, const TextParseOptions &options) : _t(t), TextParser(Text *t, const TextWithEntities &textWithEntities, const TextParseOptions &options) : _t(t),
src(text), src(textWithEntities.text),
rich(options.flags & TextParseRichText), rich(options.flags & TextParseRichText),
multiline(options.flags & TextParseMultiline), multiline(options.flags & TextParseMultiline),
maxLnkIndex(0), maxLnkIndex(0),
flags(0), flags(0),
lnkIndex(0), lnkIndex(0),
stopAfterWidth(QFIXED_MAX) { stopAfterWidth(QFIXED_MAX) {
auto preparsed = textWithEntities.entities;
if ((options.flags & TextParseLinks) && !preparsed.isEmpty()) { if ((options.flags & TextParseLinks) && !preparsed.isEmpty()) {
bool parseMentions = (options.flags & TextParseMentions); bool parseMentions = (options.flags & TextParseMentions);
bool parseHashtags = (options.flags & TextParseHashtags); bool parseHashtags = (options.flags & TextParseHashtags);
@ -573,7 +574,7 @@ public:
} else { } else {
int32 i = 0, l = preparsed.size(); int32 i = 0, l = preparsed.size();
entities.reserve(l); entities.reserve(l);
const QChar s = text.size(); const QChar s = src.size();
for (; i < l; ++i) { for (; i < l; ++i) {
auto type = preparsed.at(i).type(); auto type = preparsed.at(i).type();
if (((type == EntityInTextMention || type == EntityInTextMentionName) && !parseMentions) || if (((type == EntityInTextMention || type == EntityInTextMentionName) && !parseMentions) ||
@ -2497,7 +2498,7 @@ void Text::recountNaturalSize(bool initial, Qt::LayoutDirection optionsDir) {
} }
} }
void Text::setMarkedText(style::font font, const QString &text, const EntitiesInText &entities, const TextParseOptions &options) { void Text::setMarkedText(style::font font, const TextWithEntities &textWithEntities, const TextParseOptions &options) {
if (!_textStyle) initDefault(); if (!_textStyle) initDefault();
_font = font; _font = font;
clear(); clear();
@ -2532,7 +2533,7 @@ void Text::setMarkedText(style::font font, const QString &text, const EntitiesIn
// newText.append("\n\n").append(text); // newText.append("\n\n").append(text);
// TextParser parser(this, newText, EntitiesInText(), options); // TextParser parser(this, newText, EntitiesInText(), options);
TextParser parser(this, text, entities, options); TextParser parser(this, textWithEntities, options);
} }
recountNaturalSize(true, options.dir); recountNaturalSize(true, options.dir);
} }
@ -2932,117 +2933,133 @@ TextSelection Text::adjustSelection(TextSelection selection, TextSelectType sele
return { from, to }; return { from, to };
} }
QString Text::original(TextSelection selection, ExpandLinksMode mode) const { template <typename AppendPartCallback, typename ClickHandlerStartCallback, typename ClickHandlerFinishCallback, typename FlagsChangeCallback>
void Text::enumerateText(TextSelection selection, AppendPartCallback appendPartCallback, ClickHandlerStartCallback clickHandlerStartCallback, ClickHandlerFinishCallback clickHandlerFinishCallback, FlagsChangeCallback flagsChangeCallback) const {
if (isEmpty() || selection.empty()) { if (isEmpty() || selection.empty()) {
return QString(); return;
} }
QString result, emptyurl; int lnkIndex = 0;
result.reserve(_text.size()); uint16 lnkFrom = 0;
int32 flags = 0;
int32 lnkFrom = 0, lnkIndex = 0; for (auto i = _blocks.cbegin(), e = _blocks.cend(); true; ++i) {
for (TextBlocks::const_iterator i = _blocks.cbegin(), e = _blocks.cend(); true; ++i) { int blockLnkIndex = (i == e) ? 0 : (*i)->lnkIndex();
int32 blockLnkIndex = (i == e) ? 0 : (*i)->lnkIndex(); uint16 blockFrom = (i == e) ? _text.size() : (*i)->from();
int32 blockFrom = (i == e) ? _text.size() : (*i)->from();
if (blockLnkIndex && !_links.at(blockLnkIndex - 1)) { // ignore empty links
blockLnkIndex = 0;
}
if (blockLnkIndex != lnkIndex) {
int32 rangeFrom = qMax(int32(selection.from), lnkFrom), rangeTo = qMin(blockFrom, int32(selection.to));
if (lnkIndex && rangeTo > rangeFrom) { // write link
QStringRef r = _text.midRef(rangeFrom, rangeTo - rangeFrom);
if (lnkFrom != rangeFrom || blockFrom != rangeTo) {
result += r;
} else {
QString expanded = _links.at(lnkIndex - 1)->getExpandedLinkText(mode, r);
if (expanded.isEmpty()) {
result += r;
} else {
result += expanded;
}
}
}
lnkIndex = blockLnkIndex;
lnkFrom = blockFrom;
}
if (i == e) break;
TextBlockType type = (*i)->type();
if (type == TextBlockTSkip) continue;
if (!blockLnkIndex) {
int32 rangeFrom = qMax(selection.from, (*i)->from()), rangeTo = qMin(selection.to, uint16((*i)->from() + TextPainter::_blockLength(this, i, e)));
if (rangeTo > rangeFrom) {
result += _text.midRef(rangeFrom, rangeTo - rangeFrom);
}
}
}
return result;
}
EntitiesInText Text::originalEntities() const {
EntitiesInText result;
int32 originalLength = 0, lnkStart = 0, italicStart = 0, boldStart = 0, codeStart = 0, preStart = 0;
int32 lnkFrom = 0, lnkIndex = 0, flags = 0;
for (TextBlocks::const_iterator i = _blocks.cbegin(), e = _blocks.cend(); true; ++i) {
int32 blockLnkIndex = (i == e) ? 0 : (*i)->lnkIndex();
int32 blockFrom = (i == e) ? _text.size() : (*i)->from();
int32 blockFlags = (i == e) ? 0 : (*i)->flags(); int32 blockFlags = (i == e) ? 0 : (*i)->flags();
if (blockFlags != flags) {
if ((flags & TextBlockFItalic) && !(blockFlags & TextBlockFItalic)) { // write italic bool checkBlockFlags = (blockFrom >= selection.from) && (blockFrom <= selection.to);
result.push_back(EntityInText(EntityInTextItalic, italicStart, originalLength - italicStart)); if (checkBlockFlags && blockFlags != flags) {
} else if ((blockFlags & TextBlockFItalic) && !(flags & TextBlockFItalic)) { flagsChangeCallback(flags, blockFlags);
italicStart = originalLength;
}
if ((flags & TextBlockFSemibold) && !(blockFlags & TextBlockFSemibold)) {
result.push_back(EntityInText(EntityInTextBold, boldStart, originalLength - boldStart));
} else if ((blockFlags & TextBlockFSemibold) && !(flags & TextBlockFSemibold)) {
boldStart = originalLength;
}
if ((flags & TextBlockFCode) && !(blockFlags & TextBlockFCode)) {
result.push_back(EntityInText(EntityInTextCode, codeStart, originalLength - codeStart));
} else if ((blockFlags & TextBlockFCode) && !(flags & TextBlockFCode)) {
codeStart = originalLength;
}
if ((flags & TextBlockFPre) && !(blockFlags & TextBlockFPre)) {
result.push_back(EntityInText(EntityInTextPre, preStart, originalLength - preStart));
} else if ((blockFlags & TextBlockFPre) && !(flags & TextBlockFPre)) {
preStart = originalLength;
}
flags = blockFlags; flags = blockFlags;
} }
if (blockLnkIndex && !_links.at(blockLnkIndex - 1)) { // ignore empty links if (blockLnkIndex && !_links.at(blockLnkIndex - 1)) { // ignore empty links
blockLnkIndex = 0; blockLnkIndex = 0;
} }
if (blockLnkIndex != lnkIndex) { if (blockLnkIndex != lnkIndex) {
int32 rangeFrom = lnkFrom, rangeTo = blockFrom; if (lnkIndex) {
if (lnkIndex && rangeTo > rangeFrom) { // write link auto rangeFrom = qMax(selection.from, lnkFrom);
QStringRef r = _text.midRef(rangeFrom, rangeTo - rangeFrom); auto rangeTo = qMin(blockFrom, selection.to);
if (auto entity = _links.at(lnkIndex - 1)->getEntityInText(lnkStart, r)) { if (rangeTo > rangeFrom) { // handle click handler
result.push_back(entity); QStringRef r = _text.midRef(rangeFrom, rangeTo - rangeFrom);
originalLength += entity.length(); if (lnkFrom != rangeFrom || blockFrom != rangeTo) {
} else { appendPartCallback(r);
originalLength += r.size(); } else {
clickHandlerFinishCallback(r, _links.at(lnkIndex - 1));
}
} }
} }
lnkIndex = blockLnkIndex; lnkIndex = blockLnkIndex;
if (lnkIndex) { if (lnkIndex) {
lnkFrom = blockFrom; lnkFrom = blockFrom;
lnkStart = originalLength; clickHandlerStartCallback();
} }
} }
if (i == e) break; if (i == e || blockFrom >= selection.to) break;
TextBlockType type = (*i)->type(); if ((*i)->type() == TextBlockTSkip) continue;
if (type == TextBlockTSkip) continue;
if (!blockLnkIndex) { if (!blockLnkIndex) {
int32 rangeFrom = (*i)->from(), rangeTo = uint16((*i)->from() + TextPainter::_blockLength(this, i, e)); auto rangeFrom = qMax(selection.from, blockFrom);
auto rangeTo = qMin(selection.to, uint16(blockFrom + TextPainter::_blockLength(this, i, e)));
if (rangeTo > rangeFrom) { if (rangeTo > rangeFrom) {
originalLength += rangeTo - rangeFrom; appendPartCallback(_text.midRef(rangeFrom, rangeTo - rangeFrom));
} }
} }
} }
}
TextWithEntities Text::originalTextWithEntities(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 = [&result, &italicStart, &boldStart, &codeStart, &preStart](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 = [&result, &lnkStart]() {
lnkStart = result.text.size();
};
auto clickHandlerFinishCallback = [mode, &result, &lnkStart](const QStringRef &r, const ClickHandlerPtr &handler) {
auto expanded = handler->getExpandedLinkTextWithEntities(mode, lnkStart, r);
if (expanded.text.isEmpty()) {
result.text += r;
} else {
result.text += expanded.text;
}
if (!expanded.entities.isEmpty()) {
result.entities.append(expanded.entities);
}
};
auto appendPartCallback = [&result](const QStringRef &r) {
result.text += r;
};
enumerateText(selection, appendPartCallback, clickHandlerStartCallback, clickHandlerFinishCallback, flagsChangeCallback);
return result;
}
QString Text::originalText(TextSelection selection, ExpandLinksMode mode) const {
QString result;
result.reserve(_text.size());
auto appendPartCallback = [&result](const QStringRef &r) {
result += r;
};
auto clickHandlerStartCallback = []() {
};
auto clickHandlerFinishCallback = [mode, &result](const QStringRef &r, const ClickHandlerPtr &handler) {
auto expanded = handler->getExpandedLinkText(mode, r);
if (expanded.isEmpty()) {
result += r;
} else {
result += expanded;
}
};
auto flagsChangeCallback = [](int32 oldFlags, int32 newFlags) {
};
enumerateText(selection, appendPartCallback, clickHandlerStartCallback, clickHandlerFinishCallback, flagsChangeCallback);
return result; return result;
} }

View File

@ -97,7 +97,7 @@ public:
int32 countHeight(int32 width) const; int32 countHeight(int32 width) const;
void setText(style::font font, const QString &text, const TextParseOptions &options = _defaultOptions); void setText(style::font font, const QString &text, const TextParseOptions &options = _defaultOptions);
void setRichText(style::font font, const QString &text, TextParseOptions options = _defaultOptions, const TextCustomTagsMap &custom = TextCustomTagsMap()); void setRichText(style::font font, const QString &text, TextParseOptions options = _defaultOptions, const TextCustomTagsMap &custom = TextCustomTagsMap());
void setMarkedText(style::font font, const QString &text, const EntitiesInText &entities, const TextParseOptions &options = _defaultOptions); void setMarkedText(style::font font, const TextWithEntities &textWithEntities, const TextParseOptions &options = _defaultOptions);
void setLink(uint16 lnkIndex, const ClickHandlerPtr &lnk); void setLink(uint16 lnkIndex, const ClickHandlerPtr &lnk);
bool hasLinks() const; bool hasLinks() const;
@ -178,8 +178,9 @@ public:
int length() const { int length() const {
return _text.size(); return _text.size();
} }
QString original(TextSelection selection = AllTextSelection, ExpandLinksMode mode = ExpandLinksShortened) const;
EntitiesInText originalEntities() const; TextWithEntities originalTextWithEntities(TextSelection selection = AllTextSelection, ExpandLinksMode mode = ExpandLinksShortened) const;
QString originalText(TextSelection selection = AllTextSelection, ExpandLinksMode mode = ExpandLinksShortened) const;
bool lastDots(int32 dots, int32 maxdots = 3) { // hack for typing animation bool lastDots(int32 dots, int32 maxdots = 3) { // hack for typing animation
if (_text.size() < maxdots) return false; if (_text.size() < maxdots) return false;
@ -207,6 +208,10 @@ public:
private: private:
// Template method for originalText(), originalTextWithEntities().
template <typename AppendPartCallback, typename ClickHandlerStartCallback, typename ClickHandlerFinishCallback, typename FlagsChangeCallback>
void enumerateText(TextSelection selection, AppendPartCallback appendPartCallback, ClickHandlerStartCallback clickHandlerStartCallback, ClickHandlerFinishCallback clickHandlerFinishCallback, FlagsChangeCallback flagsChangeCallback) const;
void recountNaturalSize(bool initial, Qt::LayoutDirection optionsDir = Qt::LayoutDirectionAuto); void recountNaturalSize(bool initial, Qt::LayoutDirection optionsDir = Qt::LayoutDirectionAuto);
// clear() deletes all blocks and calls this method // clear() deletes all blocks and calls this method

View File

@ -1437,7 +1437,7 @@ MTPVector<MTPMessageEntity> linksToMTP(const EntitiesInText &links, bool sending
// Some code is duplicated in flattextarea.cpp! // Some code is duplicated in flattextarea.cpp!
void textParseEntities(QString &text, int32 flags, EntitiesInText *inOutEntities, bool rich) { void textParseEntities(QString &text, int32 flags, EntitiesInText *inOutEntities, bool rich) {
EntitiesInText mono; EntitiesInText result;
bool withHashtags = (flags & TextParseHashtags); bool withHashtags = (flags & TextParseHashtags);
bool withMentions = (flags & TextParseMentions); bool withMentions = (flags & TextParseMentions);
@ -1445,6 +1445,9 @@ void textParseEntities(QString &text, int32 flags, EntitiesInText *inOutEntities
bool withMono = (flags & TextParseMono); bool withMono = (flags & TextParseMono);
if (withMono) { // parse mono entities (code and pre) if (withMono) { // parse mono entities (code and pre)
int existingEntityIndex = 0, existingEntitiesCount = inOutEntities->size();
int existingEntityShiftLeft = 0;
QString newText; QString newText;
int32 offset = 0, matchOffset = offset, len = text.size(), commandOffset = rich ? 0 : len; int32 offset = 0, matchOffset = offset, len = text.size(), commandOffset = rich ? 0 : len;
@ -1468,7 +1471,7 @@ void textParseEntities(QString &text, int32 flags, EntitiesInText *inOutEntities
auto mCode = _reCode.match(text, matchOffset); auto mCode = _reCode.match(text, matchOffset);
if (!mPre.hasMatch() && !mCode.hasMatch()) break; if (!mPre.hasMatch() && !mCode.hasMatch()) break;
int32 preStart = mPre.hasMatch() ? mPre.capturedStart() : INT_MAX, int preStart = mPre.hasMatch() ? mPre.capturedStart() : INT_MAX,
preEnd = mPre.hasMatch() ? mPre.capturedEnd() : INT_MAX, preEnd = mPre.hasMatch() ? mPre.capturedEnd() : INT_MAX,
codeStart = mCode.hasMatch() ? mCode.capturedStart() : INT_MAX, codeStart = mCode.hasMatch() ? mCode.capturedStart() : INT_MAX,
codeEnd = mCode.hasMatch() ? mCode.capturedEnd() : INT_MAX, codeEnd = mCode.hasMatch() ? mCode.capturedEnd() : INT_MAX,
@ -1506,11 +1509,25 @@ void textParseEntities(QString &text, int32 flags, EntitiesInText *inOutEntities
continue; continue;
} }
if (newText.isEmpty()) newText.reserve(text.size());
bool addNewlineBefore = false, addNewlineAfter = false; bool addNewlineBefore = false, addNewlineAfter = false;
int32 outerStart = tagStart, outerEnd = tagEnd; int32 outerStart = tagStart, outerEnd = tagEnd;
int32 innerStart = tagStart + mTag.capturedLength(2), innerEnd = tagEnd - mTag.capturedLength(3); int32 innerStart = tagStart + mTag.capturedLength(2), innerEnd = tagEnd - mTag.capturedLength(3);
// Check if start or end sequences intersect any existing entity.
int intersectedEntityEnd = 0;
for_const (auto &entity, *inOutEntities) {
if (qMin(innerStart, entity.offset() + entity.length()) > qMax(outerStart, entity.offset()) ||
qMin(outerEnd, entity.offset() + entity.length()) > qMax(innerEnd, entity.offset())) {
intersectedEntityEnd = entity.offset() + entity.length();
break;
}
}
if (intersectedEntityEnd > 0) {
matchOffset = qMax(innerStart, intersectedEntityEnd);
continue;
}
if (newText.isEmpty()) newText.reserve(text.size());
if (pre) { if (pre) {
while (outerStart > 0 && chIsSpace(*(start + outerStart - 1), rich) && !chIsNewline(*(start + outerStart - 1))) { while (outerStart > 0 && chIsSpace(*(start + outerStart - 1), rich) && !chIsNewline(*(start + outerStart - 1))) {
--outerStart; --outerStart;
@ -1540,14 +1557,27 @@ void textParseEntities(QString &text, int32 flags, EntitiesInText *inOutEntities
} }
addNewlineAfter = (outerEnd < len && !chIsNewline(*(start + outerEnd))); addNewlineAfter = (outerEnd < len && !chIsNewline(*(start + outerEnd)));
} }
for (; existingEntityIndex < existingEntitiesCount && inOutEntities->at(existingEntityIndex).offset() < innerStart; ++existingEntityIndex) {
auto &entity = inOutEntities->at(existingEntityIndex);
result.push_back(entity);
result.back().shiftLeft(existingEntityShiftLeft);
}
if (outerStart > offset) newText.append(start + offset, outerStart - offset); if (outerStart > offset) newText.append(start + offset, outerStart - offset);
if (addNewlineBefore) newText.append('\n'); if (addNewlineBefore) newText.append('\n');
existingEntityShiftLeft += (innerStart - outerStart) - (addNewlineBefore ? 1 : 0);
int32 tagLength = innerEnd - innerStart; int entityStart = newText.size(), entityLength = innerEnd - innerStart;
mono.push_back(EntityInText(pre ? EntityInTextPre : EntityInTextCode, newText.size(), tagLength)); result.push_back(EntityInText(pre ? EntityInTextPre : EntityInTextCode, entityStart, entityLength));
newText.append(start + innerStart, tagLength); for (; existingEntityIndex < existingEntitiesCount && inOutEntities->at(existingEntityIndex).offset() <= innerEnd; ++existingEntityIndex) {
auto &entity = inOutEntities->at(existingEntityIndex);
result.push_back(entity);
result.back().shiftLeft(existingEntityShiftLeft);
}
newText.append(start + innerStart, entityLength);
if (addNewlineAfter) newText.append('\n'); if (addNewlineAfter) newText.append('\n');
existingEntityShiftLeft += (outerEnd - innerEnd) - (addNewlineAfter ? 1 : 0);
offset = matchOffset = outerEnd; offset = matchOffset = outerEnd;
} }
@ -1555,8 +1585,19 @@ void textParseEntities(QString &text, int32 flags, EntitiesInText *inOutEntities
newText.append(start + offset, len - offset); newText.append(start + offset, len - offset);
text = newText; text = newText;
} }
if (!result.isEmpty()) {
for (; existingEntityIndex < existingEntitiesCount; ++existingEntityIndex) {
auto &entity = inOutEntities->at(existingEntityIndex);
result.push_back(entity);
result.back().shiftLeft(existingEntityShiftLeft);
}
*inOutEntities = result;
result = EntitiesInText();
}
} }
int32 monoEntity = 0, monoCount = mono.size(), monoTill = 0;
int existingEntityIndex = 0, existingEntitiesCount = inOutEntities->size();
int existingEntityEnd = 0;
initLinkSets(); initLinkSets();
int32 len = text.size(), commandOffset = rich ? 0 : len; int32 len = text.size(), commandOffset = rich ? 0 : len;
@ -1572,11 +1613,11 @@ void textParseEntities(QString &text, int32 flags, EntitiesInText *inOutEntities
} }
} }
} }
QRegularExpressionMatch mDomain = _reDomain.match(text, matchOffset); auto mDomain = _reDomain.match(text, matchOffset);
QRegularExpressionMatch mExplicitDomain = _reExplicitDomain.match(text, matchOffset); auto mExplicitDomain = _reExplicitDomain.match(text, matchOffset);
QRegularExpressionMatch mHashtag = withHashtags ? _reHashtag.match(text, matchOffset) : QRegularExpressionMatch(); auto mHashtag = withHashtags ? _reHashtag.match(text, matchOffset) : QRegularExpressionMatch();
QRegularExpressionMatch mMention = withMentions ? _reMention.match(text, qMax(mentionSkip, matchOffset)) : QRegularExpressionMatch(); auto mMention = withMentions ? _reMention.match(text, qMax(mentionSkip, matchOffset)) : QRegularExpressionMatch();
QRegularExpressionMatch mBotCommand = withBotCommands ? _reBotCommand.match(text, matchOffset) : QRegularExpressionMatch(); auto mBotCommand = withBotCommands ? _reBotCommand.match(text, matchOffset) : QRegularExpressionMatch();
EntityInTextType lnkType = EntityInTextUrl; EntityInTextType lnkType = EntityInTextUrl;
int32 lnkStart = 0, lnkLength = 0; int32 lnkStart = 0, lnkLength = 0;
@ -1735,19 +1776,23 @@ void textParseEntities(QString &text, int32 flags, EntitiesInText *inOutEntities
lnkLength = (p - start) - lnkStart; lnkLength = (p - start) - lnkStart;
} }
} }
for (; monoEntity < monoCount && mono[monoEntity].offset() <= lnkStart; ++monoEntity) { for (; existingEntityIndex < existingEntitiesCount && inOutEntities->at(existingEntityIndex).offset() <= lnkStart; ++existingEntityIndex) {
monoTill = qMax(monoTill, mono[monoEntity].offset() + mono[monoEntity].length()); auto &entity = inOutEntities->at(existingEntityIndex);
inOutEntities->push_back(mono[monoEntity]); accumulate_max(existingEntityEnd, entity.offset() + entity.length());
result.push_back(entity);
} }
if (lnkStart >= monoTill) { if (lnkStart >= existingEntityEnd) {
inOutEntities->push_back(EntityInText(lnkType, lnkStart, lnkLength)); inOutEntities->push_back(EntityInText(lnkType, lnkStart, lnkLength));
} }
offset = matchOffset = lnkStart + lnkLength; offset = matchOffset = lnkStart + lnkLength;
} }
for (; monoEntity < monoCount; ++monoEntity) { if (!result.isEmpty()) {
monoTill = qMax(monoTill, mono[monoEntity].offset() + mono[monoEntity].length()); for (; existingEntityIndex < existingEntitiesCount; ++existingEntityIndex) {
inOutEntities->push_back(mono[monoEntity]); auto &entity = inOutEntities->at(existingEntityIndex);
result.push_back(entity);
}
*inOutEntities = result;
} }
} }

View File

@ -79,6 +79,9 @@ public:
} }
} }
} }
void shiftRight(int shift) {
_offset += shift;
}
void updateTextEnd(int textEnd) { void updateTextEnd(int textEnd) {
if (_offset > textEnd) { if (_offset > textEnd) {
_offset = textEnd; _offset = textEnd;
@ -108,7 +111,19 @@ private:
QString _data; QString _data;
}; };
typedef QList<EntityInText> EntitiesInText;
struct TextWithEntities {
QString text;
EntitiesInText entities;
};
inline void appendTextWithEntities(TextWithEntities &to, TextWithEntities &&append) {
int entitiesShiftRight = to.text.size();
for (auto &entity : append.entities) {
entity.shiftRight(entitiesShiftRight);
}
to.text += append.text;
to.entities += append.entities;
}
// text preprocess // text preprocess
QString textClean(const QString &text); QString textClean(const QString &text);