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);
}
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->setViewsCount(m.has_views() ? m.vviews.v : -1);
existing->addToOverview(AddToOverviewNew);

View File

@ -414,7 +414,7 @@ EditCaptionBox::EditCaptionBox(HistoryItem *msg) : AbstractBox(st::boxWideWidth)
image = doc->thumb;
} break;
}
caption = media->getCaption();
caption = media->getCaption().text;
}
if ((!_animated && (dimensions.isEmpty() || doc)) || image->isNull()) {
_animated = false;
@ -492,7 +492,8 @@ EditCaptionBox::EditCaptionBox(HistoryItem *msg) : AbstractBox(st::boxWideWidth)
_field->setMaxLength(MaxPhotoCaption);
_field->setCtrlEnterSubmit(CtrlEnterSubmitBoth);
} 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->setMaxLength(MaxMessageSize); // entities can make text in input field larger but still valid
_field->setCtrlEnterSubmit(cCtrlEnter() ? CtrlEnterSubmitCtrlEnter : CtrlEnterSubmitEnter);

View File

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

View File

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

View File

@ -75,18 +75,23 @@ void UrlClickHandler::doOpen(QString url) {
}
QString UrlClickHandler::getExpandedLinkText(ExpandLinksMode mode, const QStringRef &textPart) const {
if (mode == ExpandLinksNone) {
return QString();
QString result;
if (mode != ExpandLinksNone) {
result = _originalUrl;
}
return _originalUrl;
return result;
}
EntityInText UrlClickHandler::getEntityInText(int offset, const QStringRef &textPart) const {
auto u = _originalUrl;
if (isEmail(u)) {
return EntityInText(EntityInTextUrl, offset, u.size());
TextWithEntities UrlClickHandler::getExpandedLinkTextWithEntities(ExpandLinksMode mode, int entityOffset, const QStringRef &textPart) const {
TextWithEntities result;
auto entityType = isEmail(_originalUrl) ? EntityInTextEmail : EntityInTextUrl;
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 {
@ -102,14 +107,20 @@ void HiddenUrlClickHandler::onClick(Qt::MouseButton button) const {
}
QString HiddenUrlClickHandler::getExpandedLinkText(ExpandLinksMode mode, const QStringRef &textPart) const {
if (mode != ExpandLinksAll) {
return QString();
QString result;
if (mode == ExpandLinksAll) {
result = textPart.toString() + qsl(" (") + url() + ')';
}
return textPart.toString() + qsl(" (") + url() + ')';
return result;
}
EntityInText HiddenUrlClickHandler::getEntityInText(int offset, const QStringRef &textPart) const {
return EntityInText(EntityInTextCustomUrl, offset, textPart.size(), url());
TextWithEntities HiddenUrlClickHandler::getExpandedLinkTextWithEntities(ExpandLinksMode mode, int entityOffset, const QStringRef &textPart) const {
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 {
@ -122,8 +133,8 @@ void MentionClickHandler::onClick(Qt::MouseButton button) const {
}
}
EntityInText MentionClickHandler::getEntityInText(int offset, const QStringRef &textPart) const {
return EntityInText(EntityInTextMention, offset, textPart.size());
TextWithEntities MentionClickHandler::getExpandedLinkTextWithEntities(ExpandLinksMode mode, int entityOffset, const QStringRef &textPart) const {
return simpleTextWithEntity({ EntityInTextMention, entityOffset, textPart.size() });
}
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);
return EntityInText(EntityInTextMentionName, offset, textPart.size(), data);
return simpleTextWithEntity({ EntityInTextMentionName, entityOffset, textPart.size(), data });
}
QString MentionNameClickHandler::tooltip() const {
@ -159,8 +170,8 @@ void HashtagClickHandler::onClick(Qt::MouseButton button) const {
}
}
EntityInText HashtagClickHandler::getEntityInText(int offset, const QStringRef &textPart) const {
return EntityInText(EntityInTextHashtag, offset, textPart.size());
TextWithEntities HashtagClickHandler::getExpandedLinkTextWithEntities(ExpandLinksMode mode, int entityOffset, const QStringRef &textPart) const {
return simpleTextWithEntity({ EntityInTextHashtag, entityOffset, textPart.size() });
}
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 {
return EntityInText(EntityInTextHashtag, offset, textPart.size());
TextWithEntities BotCommandClickHandler::getExpandedLinkTextWithEntities(ExpandLinksMode mode, int entityOffset, const QStringRef &textPart) const {
return simpleTextWithEntity({ EntityInTextHashtag, entityOffset, textPart.size() });
}

View File

@ -70,7 +70,7 @@ public:
}
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);
void onClick(Qt::MouseButton button) const override {
@ -118,7 +118,7 @@ public:
void onClick(Qt::MouseButton button) 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;
EntityInText getEntityInText(int offset, const QStringRef &textPart) const override;
TextWithEntities getExpandedLinkTextWithEntities(ExpandLinksMode mode, int entityOffset, const QStringRef &textPart) const override;
protected:
QString url() const override {
@ -157,7 +157,7 @@ public:
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;
@ -181,7 +181,7 @@ public:
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:
QString url() const override {
@ -204,7 +204,7 @@ public:
return _cmd;
}
EntityInText getEntityInText(int offset, const QStringRef &textPart) const override;
TextWithEntities getExpandedLinkTextWithEntities(ExpandLinksMode mode, int entityOffset, const QStringRef &textPart) const override;
protected:
QString url() const override {

View File

@ -1032,7 +1032,7 @@ HistoryItem *History::createItem(const MTPMessage &msg, bool applyServiceAction,
EntitiesInText entities;
textParseEntities(text, _historyTextNoMonoOptions.flags, &entities);
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) {
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 {
@ -3095,22 +3095,21 @@ void HistoryItem::setId(MsgId newId) {
}
bool HistoryItem::canEdit(const QDateTime &cur) const {
int32 s = date.secsTo(cur);
auto channel = _history->peer->asChannel();
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 (HistoryMedia *media = msg->getMedia()) {
HistoryMediaType t = media->type();
if (t != MediaTypePhoto &&
t != MediaTypeVideo &&
t != MediaTypeFile &&
t != MediaTypeGif &&
t != MediaTypeMusicFile &&
t != MediaTypeVoiceFile &&
t != MediaTypeWebPage) {
auto type = media->type();
if (type != MediaTypePhoto &&
type != MediaTypeVideo &&
type != MediaTypeFile &&
type != MediaTypeGif &&
type != MediaTypeMusicFile &&
type != MediaTypeVoiceFile &&
type != MediaTypeWebPage) {
return false;
}
}
@ -3304,18 +3303,24 @@ int32 gifMaxStatusWidth(DocumentData *document) {
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) {
return caption.original(selection, ExpandLinksAll);
return caption.originalTextWithEntities(selection, ExpandLinksAll);
}
QString result;
result.reserve(5 + attachType.size() + caption.length());
result.append(qstr("[ ")).append(attachType).append(qstr(" ]"));
TextWithEntities result, original;
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;
}
} // namespace
void HistoryFileMedia::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) {
@ -3735,10 +3740,10 @@ void HistoryPhoto::detachFromParent() {
}
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);
}
@ -3981,10 +3986,10 @@ void HistoryVideo::setStatusSize(int32 newSize) 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);
}
@ -4472,13 +4477,13 @@ QString HistoryDocument::inDialogsText() const {
}
if (auto captioned = Get<HistoryDocumentCaptioned>()) {
if (!captioned->_caption.isEmpty()) {
result.append(' ').append(captioned->_caption.original(AllTextSelection, ExpandLinksNone));
result.append(' ').append(captioned->_caption.originalText(AllTextSelection, ExpandLinksNone));
}
}
return result;
}
QString HistoryDocument::selectedText(TextSelection selection) const {
TextWithEntities HistoryDocument::selectedText(TextSelection selection) const {
const Text emptyCaption;
const Text *caption = &emptyCaption;
if (auto captioned = Get<HistoryDocumentCaptioned>()) {
@ -4930,10 +4935,10 @@ HistoryTextState HistoryGif::getState(int x, int y, HistoryStateRequest request)
}
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);
}
@ -5250,11 +5255,11 @@ QString HistorySticker::inDialogsText() const {
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) {
return QString();
return TextWithEntities();
}
return qsl("[ ") + inDialogsText() + qsl(" ]");
return { qsl("[ ") + inDialogsText() + qsl(" ]"), EntitiesInText() };
}
void HistorySticker::attachToParent() {
@ -5435,11 +5440,11 @@ QString HistoryContact::inDialogsText() const {
return lang(lng_in_dlg_contact);
}
QString HistoryContact::selectedText(TextSelection selection) const {
TextWithEntities HistoryContact::selectedText(TextSelection selection) const {
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() {
@ -5956,18 +5961,21 @@ QString HistoryWebPage::inDialogsText() const {
return QString();
}
QString HistoryWebPage::selectedText(TextSelection selection) const {
TextWithEntities HistoryWebPage::selectedText(TextSelection selection) const {
if (selection == FullSelection) {
return QString();
return TextWithEntities();
}
auto titleResult = _title.original(selection, ExpandLinksAll);
auto descriptionResult = _description.original(toDescriptionSelection(selection), ExpandLinksAll);
if (titleResult.isEmpty()) {
auto titleResult = _title.originalTextWithEntities(selection, ExpandLinksAll);
auto descriptionResult = _description.originalTextWithEntities(toDescriptionSelection(selection), ExpandLinksAll);
if (titleResult.text.isEmpty()) {
return descriptionResult;
} else if (descriptionResult.isEmpty()) {
} else if (descriptionResult.text.isEmpty()) {
return titleResult;
}
return titleResult + '\n' + descriptionResult;
titleResult.text += '\n';
appendTextWithEntities(titleResult, std_::move(descriptionResult));
return titleResult;
}
ImagePtr HistoryWebPage::replyPreview() {
@ -6399,24 +6407,31 @@ TextSelection HistoryLocation::adjustSelection(TextSelection selection, TextSele
}
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) {
auto result = qsl("[ ") + lang(lng_maps_point) + qsl(" ]\n");
TextWithEntities result = { qsl("[ ") + lang(lng_maps_point) + qsl(" ]\n"), EntitiesInText() };
auto info = selectedText(AllTextSelection);
if (!info.isEmpty()) result.append(info).append('\n');
return result + _link->dragText();
if (!info.text.isEmpty()) {
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));
if (titleResult.isEmpty()) {
auto titleResult = _title.originalTextWithEntities(selection);
auto descriptionResult = _description.originalTextWithEntities(toDescriptionSelection(selection));
if (titleResult.text.isEmpty()) {
return descriptionResult;
} else if (descriptionResult.isEmpty()) {
} else if (descriptionResult.text.isEmpty()) {
return titleResult;
}
return titleResult + '\n' + descriptionResult;
titleResult.text += '\n';
appendTextWithEntities(titleResult, std_::move(descriptionResult));
return titleResult;
}
int32 HistoryLocation::fullWidth() const {
@ -6729,7 +6744,12 @@ HistoryMessage::HistoryMessage(History *history, const MTPDmessage &msg)
QString text(textClean(qs(msg.vmessage)));
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)
@ -6755,14 +6775,14 @@ HistoryMessage::HistoryMessage(History *history, MsgId id, MTPDmessage::Flags fl
if (HistoryMedia *mediaOriginal = fwd->getMedia()) {
_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) {
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)
@ -6770,7 +6790,7 @@ HistoryMessage::HistoryMessage(History *history, MsgId msgId, MTPDmessage::Flags
createComponentsHelper(flags, replyTo, viaBotId, markup);
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)
@ -6778,7 +6798,7 @@ HistoryMessage::HistoryMessage(History *history, MsgId msgId, MTPDmessage::Flags
createComponentsHelper(flags, replyTo, viaBotId, markup);
_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) {
@ -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()) {
_flags |= MTPDmessage::Flag::f_edit_date;
if (!Has<HistoryMessageEdited>()) {
@ -7083,7 +7098,11 @@ void HistoryMessage::applyEdition(const MTPDmessage &message) {
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);
setReplyMarkup(message.has_reply_markup() ? (&message.vreply_markup) : nullptr);
setViewsCount(message.has_views() ? message.vviews.v : -1);
@ -7157,36 +7176,44 @@ void HistoryMessage::eraseFromOverview() {
}
}
QString HistoryMessage::selectedText(TextSelection selection) const {
QString result, textResult, mediaResult;
TextWithEntities HistoryMessage::selectedText(TextSelection selection) const {
TextWithEntities result, textResult, mediaResult;
if (selection == FullSelection) {
textResult = _text.original(AllTextSelection, ExpandLinksAll);
textResult = _text.originalTextWithEntities(AllTextSelection, ExpandLinksAll);
} else {
textResult = _text.original(selection, ExpandLinksAll);
textResult = _text.originalTextWithEntities(selection, ExpandLinksAll);
}
if (_media) {
mediaResult = _media->selectedText(toMediaSelection(selection));
}
if (textResult.isEmpty()) {
if (textResult.text.isEmpty()) {
result = mediaResult;
} else if (mediaResult.isEmpty()) {
} else if (mediaResult.text.isEmpty()) {
result = textResult;
} 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 (selection == FullSelection) {
QString fwdinfo = fwd->_text.original(AllTextSelection, ExpandLinksAll), wrapped;
wrapped.reserve(fwdinfo.size() + 4 + result.size());
wrapped.append('[').append(fwdinfo).append(qsl("]\n")).append(result);
auto fwdinfo = fwd->_text.originalTextWithEntities(AllTextSelection, ExpandLinksAll);
TextWithEntities wrapped;
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;
}
}
if (auto reply = Get<HistoryMessageReply>()) {
if (selection == FullSelection && reply->replyToMsg) {
QString wrapped;
wrapped.reserve(lang(lng_in_reply_to).size() + reply->replyToMsg->author()->name.size() + 4 + result.size());
wrapped.append('[').append(lang(lng_in_reply_to)).append(' ').append(reply->replyToMsg->author()->name).append(qsl("]\n")).append(result);
TextWithEntities wrapped;
wrapped.text.reserve(lang(lng_in_reply_to).size() + reply->replyToMsg->author()->name.size() + 4 + result.text.size());
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;
}
}
@ -7194,7 +7221,7 @@ QString HistoryMessage::selectedText(TextSelection selection) 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 {
@ -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));
if (_media && _media->isDisplayed()) {
_text.setMarkedText(st::msgFont, text, entities, itemTextOptions(this));
_text.setMarkedText(st::msgFont, textWithEntities, itemTextOptions(this));
} else {
_text.setMarkedText(st::msgFont, text + skipBlock(), entities, itemTextOptions(this));
_text.setMarkedText(st::msgFont, { textWithEntities.text + skipBlock(), textWithEntities.entities }, itemTextOptions(this));
}
textstyleRestore();
for_const (const auto &entity, entities) {
for_const (auto &entity, textWithEntities.entities) {
auto type = entity.type();
if (type == EntityInTextUrl || type == EntityInTextCustomUrl || type == EntityInTextEmail) {
_flags |= MTPDmessage_ClientFlag::f_has_text_links;
@ -7286,15 +7313,14 @@ void HistoryMessage::setReplyMarkup(const MTPReplyMarkup *markup) {
}
}
QString HistoryMessage::originalText() const {
return emptyText() ? QString() : _text.original();
TextWithEntities HistoryMessage::originalText() const {
if (emptyText()) {
return { QString(), EntitiesInText() };
}
return _text.originalTextWithEntities();
}
EntitiesInText HistoryMessage::originalEntities() const {
return emptyText() ? EntitiesInText() : _text.originalEntities();
}
bool HistoryMessage::textHasLinks() {
bool HistoryMessage::textHasLinks() const {
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;
}
if (mediaText.isEmpty()) {
QString original = pinned->msg->originalText();
QString original = pinned->msg->originalText().text;
int32 cutat = 0, limit = PinnedMessageTextLimit, size = original.size();
for (; limit > 0;) {
--limit;
@ -8192,12 +8218,12 @@ void HistoryService::countPositionAndSize(int32 &left, int32 &width) const {
width = maxwidth - st::msgServiceMargin.left() - st::msgServiceMargin.left();
}
QString HistoryService::selectedText(TextSelection selection) const {
return _text.original((selection == FullSelection) ? AllTextSelection : selection);
TextWithEntities HistoryService::selectedText(TextSelection selection) const {
return _text.originalTextWithEntities((selection == FullSelection) ? AllTextSelection : selection);
}
QString HistoryService::inDialogsText() const {
return _text.original(AllTextSelection, ExpandLinksNone);
return _text.originalText(AllTextSelection, ExpandLinksNone);
}
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 msg = _text.original();
QString msg = _text.originalText();
if (msg.size() > 0xFF) msg = msg.mid(0, 0xFF) + qsl("...");
return msg;
}

View File

@ -1293,8 +1293,8 @@ public:
}
virtual void previousItemChanged();
virtual QString selectedText(TextSelection selection) const {
return qsl("[-]");
virtual TextWithEntities selectedText(TextSelection selection) const {
return { qsl("[-]"), EntitiesInText() };
}
virtual QString inDialogsText() const {
return qsl("-");
@ -1302,6 +1302,9 @@ public:
virtual QString inReplyText() const {
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 {
}
@ -1363,15 +1366,9 @@ public:
virtual HistoryMedia *getMedia() const {
return nullptr;
}
virtual void setText(const QString &text, const EntitiesInText &links) {
virtual void setText(const TextWithEntities &textWithEntities) {
}
virtual QString originalText() const {
return QString();
}
virtual EntitiesInText originalEntities() const {
return EntitiesInText();
}
virtual bool textHasLinks() {
virtual bool textHasLinks() const {
return false;
}
@ -1663,7 +1660,7 @@ public:
virtual HistoryMediaType type() 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 {
return (x >= 0 && y >= 0 && x < _width && y < _height);
@ -1750,8 +1747,8 @@ public:
virtual ImagePtr replyPreview() {
return ImagePtr();
}
virtual QString getCaption() const {
return QString();
virtual TextWithEntities getCaption() const {
return TextWithEntities();
}
virtual bool needsBubble() const = 0;
virtual bool customInfoLayout() const = 0;
@ -1900,7 +1897,7 @@ public:
}
QString inDialogsText() const override;
QString selectedText(TextSelection selection) const override;
TextWithEntities selectedText(TextSelection selection) const override;
PhotoData *photo() const {
return _data;
@ -1917,8 +1914,8 @@ public:
}
ImagePtr replyPreview() override;
QString getCaption() const override {
return _caption.original();
TextWithEntities getCaption() const override {
return _caption.originalTextWithEntities();
}
bool needsBubble() const override {
if (!_caption.isEmpty()) {
@ -1980,7 +1977,7 @@ public:
}
QString inDialogsText() const override;
QString selectedText(TextSelection selection) const override;
TextWithEntities selectedText(TextSelection selection) const override;
DocumentData *getDocument() override {
return _data;
@ -2000,8 +1997,8 @@ public:
}
ImagePtr replyPreview() override;
QString getCaption() const override {
return _caption.original();
TextWithEntities getCaption() const override {
return _caption.originalTextWithEntities();
}
bool needsBubble() const override {
if (!_caption.isEmpty()) {
@ -2103,7 +2100,7 @@ public:
}
QString inDialogsText() const override;
QString selectedText(TextSelection selection) const override;
TextWithEntities selectedText(TextSelection selection) const override;
bool uploading() const override {
return _data->uploading();
@ -2124,11 +2121,11 @@ public:
}
ImagePtr replyPreview() override;
QString getCaption() const override {
TextWithEntities getCaption() const override {
if (const HistoryDocumentCaptioned *captioned = Get<HistoryDocumentCaptioned>()) {
return captioned->_caption.original();
return captioned->_caption.originalTextWithEntities();
}
return QString();
return TextWithEntities();
}
bool needsBubble() const override {
return true;
@ -2190,7 +2187,7 @@ public:
}
QString inDialogsText() const override;
QString selectedText(TextSelection selection) const override;
TextWithEntities selectedText(TextSelection selection) const override;
bool uploading() const override {
return _data->uploading();
@ -2217,8 +2214,8 @@ public:
}
ImagePtr replyPreview() override;
QString getCaption() const override {
return _caption.original();
TextWithEntities getCaption() const override {
return _caption.originalTextWithEntities();
}
bool needsBubble() const override {
if (!_caption.isEmpty()) {
@ -2288,7 +2285,7 @@ public:
}
QString inDialogsText() const override;
QString selectedText(TextSelection selection) const override;
TextWithEntities selectedText(TextSelection selection) const override;
DocumentData *getDocument() override {
return _data;
@ -2357,7 +2354,7 @@ public:
}
QString inDialogsText() const override;
QString selectedText(TextSelection selection) const override;
TextWithEntities selectedText(TextSelection selection) const override;
void attachToParent() override;
void detachFromParent() override;
@ -2425,7 +2422,7 @@ public:
}
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 clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override;
@ -2559,7 +2556,7 @@ public:
}
QString inDialogsText() const override;
QString selectedText(TextSelection selection) const override;
TextWithEntities selectedText(TextSelection selection) const override;
bool needsBubble() const override {
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) {
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) {
return _create(history, msgId, flags, replyTo, viaBotId, date, from, msg, 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, 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) {
return _create(history, msgId, flags, replyTo, viaBotId, date, from, doc, caption, markup);
@ -2685,13 +2682,12 @@ public:
int32 addToOverview(AddToOverviewMethod method) override;
void eraseFromOverview();
QString selectedText(TextSelection selection) const override;
TextWithEntities selectedText(TextSelection selection) const override;
QString inDialogsText() const override;
HistoryMedia *getMedia() const override;
void setText(const QString &text, const EntitiesInText &entities) override;
QString originalText() const override;
EntitiesInText originalEntities() const override;
bool textHasLinks() override;
void setText(const TextWithEntities &textWithEntities) override;
TextWithEntities originalText() const override;
bool textHasLinks() const override;
int32 infoWidth() const override {
int32 result = _timeWidth;
@ -2753,7 +2749,7 @@ private:
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, 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, PhotoData *photo, const QString &caption, const MTPReplyMarkup &markup); // local photo
friend class HistoryItemInstantiated<HistoryMessage>;
@ -2902,7 +2898,7 @@ public:
bool serviceMsg() const override {
return true;
}
QString selectedText(TextSelection selection) const override;
TextWithEntities selectedText(TextSelection selection) const override;
QString inDialogsText() const override;
QString inReplyText() const override;
@ -2939,8 +2935,8 @@ public:
HistoryTextState getState(int x, int y, HistoryStateRequest request) const override;
QString selectedText(TextSelection selection) const override {
return QString();
TextWithEntities selectedText(TextSelection selection) const override {
return { QString(), EntitiesInText() };
}
HistoryItemType type() const override {
return HistoryItemGroup;
@ -2989,8 +2985,8 @@ public:
void draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const override;
HistoryTextState getState(int x, int y, HistoryStateRequest request) const override;
QString selectedText(TextSelection selection) const override {
return QString();
TextWithEntities selectedText(TextSelection selection) const override {
return { QString(), EntitiesInText() };
}
HistoryItemType type() const override {
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 "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
HistoryInner::HistoryInner(HistoryWidget *historyWidget, ScrollArea *scroll, History *history) : TWidget(nullptr)
@ -704,24 +752,21 @@ void HistoryInner::onDragExec() {
}
}
ClickHandlerPtr pressedHandler = ClickHandler::getPressed();
QString sel;
TextWithEntities sel;
QList<QUrl> urls;
if (uponSelected) {
sel = getSelectedText();
} else if (pressedHandler) {
sel = pressedHandler->dragText();
sel = { pressedHandler->dragText(), EntitiesInText() };
//if (!sel.isEmpty() && sel.at(0) != '/' && sel.at(0) != '@' && sel.at(0) != '#') {
// urls.push_back(QUrl::fromEncoded(sel.toUtf8())); // Google Chrome crashes in Mac OS X O_o
//}
}
if (!sel.isEmpty()) {
if (auto mimeData = mimeDataFromTextWithEntities(sel)) {
updateDragSelection(0, 0, false);
_widget->noSelectingScroll();
QDrag *drag = new QDrag(App::wnd());
QMimeData *mimeData = new QMimeData;
mimeData->setText(sel);
if (!urls.isEmpty()) mimeData->setUrls(urls);
if (uponSelected && !_selected.isEmpty() && _selected.cbegin().value() == FullSelection && !Adaptive::OneColumn()) {
mimeData->setData(qsl("application/x-td-forward-selected"), "1");
@ -748,7 +793,7 @@ void HistoryInner::onDragExec() {
}
if (!forwardMimeType.isEmpty()) {
QDrag *drag = new QDrag(App::wnd());
QMimeData *mimeData = new QMimeData;
QMimeData *mimeData = new QMimeData();
mimeData->setData(forwardMimeType, "1");
if (DocumentData *document = (pressedMedia ? pressedMedia->getDocument() : nullptr)) {
@ -1124,10 +1169,7 @@ void HistoryInner::onMenuDestroy(QObject *obj) {
}
void HistoryInner::copySelectedText() {
QString sel = getSelectedText();
if (!sel.isEmpty()) {
QApplication::clipboard()->setText(sel);
}
setToClipboard(getSelectedText());
}
void HistoryInner::copyContextUrl() {
@ -1217,9 +1259,12 @@ void HistoryInner::copyContextText() {
return;
}
QString contextMenuText = item->selectedText(FullSelection);
if (!contextMenuText.isEmpty()) {
QApplication::clipboard()->setText(contextMenuText);
setToClipboard(item->selectedText(FullSelection));
}
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();
}
QString HistoryInner::getSelectedText() const {
TextWithEntities HistoryInner::getSelectedText() const {
SelectedItems sel = _selected;
if (_dragAction == Selecting && _dragSelFrom && _dragSelTo) {
applyDragSelection(&sel);
}
if (sel.isEmpty()) return QString();
if (sel.isEmpty()) {
return TextWithEntities();
}
if (sel.cbegin().value() != FullSelection) {
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"));
QMap<int32, QString> texts;
for (SelectedItems::const_iterator i = sel.cbegin(), e = sel.cend(); i != e; ++i) {
QMap<int, TextWithEntities> texts;
for (auto i = sel.cbegin(), e = sel.cend(); i != e; ++i) {
HistoryItem *item = i.key();
if (item->detached()) continue;
QString text, sel = item->selectedText(FullSelection), time = item->date.toString(timeFormat);
int32 size = item->author()->name.size() + time.size() + sel.size();
text.reserve(size);
QString time = item->date.toString(timeFormat);
TextWithEntities part, unwrapped = item->selectedText(FullSelection);
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) {
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;
}
}
QString result, sep(qsl("\n\n"));
result.reserve(fullSize + (texts.size() - 1) * 2);
for (QMap<int32, QString>::const_iterator i = texts.cbegin(), e = texts.cend(); i != e; ++i) {
result.append(i.value());
TextWithEntities result;
auto sep = qsl("\n\n");
result.text.reserve(fullSize + (texts.size() - 1) * sep.size());
for (auto i = texts.begin(), e = texts.end(); i != e; ++i) {
appendTextWithEntities(result, std_::move(i.value()));
if (i + 1 != e) {
result.append(sep);
result.text.append(sep);
}
}
return result;
@ -2027,7 +2078,7 @@ QString HistoryInner::tooltipText() const {
} else if (_dragCursorState == HistoryInForwardedCursorState && _dragAction == NoDrag) {
if (App::hoveredItem()) {
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()) {
@ -2679,7 +2730,7 @@ bool HistoryHider::offerPeer(PeerId peer) {
}
QString HistoryHider::offeredText() const {
return toText.original();
return toText.originalText();
}
bool HistoryHider::wasOffered() const {
@ -2772,32 +2823,6 @@ TextWithTags::Tags textTagsFromEntities(const EntitiesInText &entities) {
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)
, _fieldBarCancel(this, st::replyCancel)
, _scroll(this, st::historyScroll, false)
@ -6258,8 +6283,8 @@ void HistoryWidget::onPhotoUploaded(const FullMsgId &newId, bool silent, const M
if (silentPost) {
sendFlags |= MTPmessages_SendMedia::Flag::f_silent;
}
QString caption = item->getMedia() ? item->getMedia()->getCaption() : QString();
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);
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.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) {
sendFlags |= MTPmessages_SendMedia::Flag::f_silent;
}
QString caption = item->getMedia() ? item->getMedia()->getCaption() : QString();
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);
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.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) {
sendFlags |= MTPmessages_SendMedia::Flag::f_silent;
}
QString caption = item->getMedia() ? item->getMedia()->getCaption() : QString();
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);
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.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();
}
auto originalText = to->originalText();
auto originalEntities = to->originalEntities();
TextWithTags original = { textApplyEntities(originalText, originalEntities), textTagsFromEntities(originalEntities) };
MessageCursor cursor = { original.text.size(), original.text.size(), QFIXED_MAX };
_history->setEditDraft(std_::make_unique<HistoryEditDraft>(original, to->id, cursor, false));
auto original = to->originalText();
auto editText = textApplyEntities(original.text, original.entities);
auto editTags = textTagsFromEntities(original.entities);
TextWithTags editData = { editText, editTags };
MessageCursor cursor = { editText.size(), editText.size(), QFIXED_MAX };
_history->setEditDraft(std_::make_unique<HistoryEditDraft>(editData, to->id, cursor, false));
applyDraft(false);
_previewData = 0;
@ -7755,10 +7781,11 @@ void HistoryWidget::onCancel() {
if (_inlineBotCancel) {
onInlineBotCancel();
} else if (_editMsgId) {
auto originalText = _replyEditMsg ? _replyEditMsg->originalText() : QString();
auto originalEntities = _replyEditMsg ? _replyEditMsg->originalEntities() : EntitiesInText();
TextWithTags original = { textApplyEntities(originalText, originalEntities), textTagsFromEntities(originalEntities) };
if (_replyEditMsg && original != _field.getTextWithTags()) {
auto original = _replyEditMsg ? _replyEditMsg->originalText() : TextWithEntities();
auto editText = textApplyEntities(original.text, original.entities);
auto editTags = textTagsFromEntities(original.entities);
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));
connect(box, SIGNAL(confirmed()), this, SLOT(onFieldBarCancel()));
Ui::showLayer(box);

View File

@ -57,7 +57,7 @@ public:
void keyPressEvent(QKeyEvent *e) override;
void showContextMenu(QContextMenuEvent *e, bool showFromTouch = false);
QString getSelectedText() const;
TextWithEntities getSelectedText() const;
void dragActionStart(const QPoint &screenPos, Qt::MouseButton button = Qt::LeftButton);
void dragActionUpdate(const QPoint &screenPos);
@ -144,6 +144,8 @@ private:
HistoryItem *nextItem(HistoryItem *item);
void updateDragSelection(HistoryItem *dragSelFrom, HistoryItem *dragSelTo, bool dragSelecting, bool force = false);
void setToClipboard(const TextWithEntities &forClipboard);
PeerData *_peer = nullptr;
History *_migrated = 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
if (peerId) {
if (HistoryItem *item = App::histItemById(peerToChannel(peerId), d.vid.v)) {
item->setText(text, d.has_entities() ? entitiesFromMTP(d.ventities.c_vector().v) : EntitiesInText());
if (auto item = App::histItemById(peerToChannel(peerId), d.vid.v)) {
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->addToOverview(AddToOverviewNew);
}

View File

@ -912,7 +912,7 @@ void MediaView::displayPhoto(PhotoData *photo, HistoryItem *item) {
_caption = Text();
if (HistoryMessage *itemMsg = item ? item->toHistoryMessage() : nullptr) {
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) {
AddComponents(Info::Bit());
QString text = _parent->originalText(), mainUrl;
EntitiesInText entities = _parent->originalEntities();
const auto textWithEntities = _parent->originalText();
QString mainUrl;
auto text = textWithEntities.text;
auto &entities = textWithEntities.entities;
int32 from = 0, till = text.size(), lnk = entities.size();
for_const (const auto &entity, entities) {
auto type = entity.type();

View File

@ -1011,7 +1011,8 @@ void InputArea::processDocumentContentsChange(int position, int charsAdded) {
if (!_inner.document()->pageSize().isNull()) {
_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);
if (emoji) {
insertEmoji(emoji, c);

View File

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

View File

@ -555,14 +555,15 @@ public:
}
parse(options);
}
TextParser(Text *t, const QString &text, const EntitiesInText &preparsed, const TextParseOptions &options) : _t(t),
src(text),
TextParser(Text *t, const TextWithEntities &textWithEntities, const TextParseOptions &options) : _t(t),
src(textWithEntities.text),
rich(options.flags & TextParseRichText),
multiline(options.flags & TextParseMultiline),
maxLnkIndex(0),
flags(0),
lnkIndex(0),
stopAfterWidth(QFIXED_MAX) {
auto preparsed = textWithEntities.entities;
if ((options.flags & TextParseLinks) && !preparsed.isEmpty()) {
bool parseMentions = (options.flags & TextParseMentions);
bool parseHashtags = (options.flags & TextParseHashtags);
@ -573,7 +574,7 @@ public:
} else {
int32 i = 0, l = preparsed.size();
entities.reserve(l);
const QChar s = text.size();
const QChar s = src.size();
for (; i < l; ++i) {
auto type = preparsed.at(i).type();
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();
_font = font;
clear();
@ -2532,7 +2533,7 @@ void Text::setMarkedText(style::font font, const QString &text, const EntitiesIn
// newText.append("\n\n").append(text);
// TextParser parser(this, newText, EntitiesInText(), options);
TextParser parser(this, text, entities, options);
TextParser parser(this, textWithEntities, options);
}
recountNaturalSize(true, options.dir);
}
@ -2932,117 +2933,133 @@ TextSelection Text::adjustSelection(TextSelection selection, TextSelectType sele
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()) {
return QString();
return;
}
QString result, emptyurl;
result.reserve(_text.size());
int32 lnkFrom = 0, lnkIndex = 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();
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();
int lnkIndex = 0;
uint16 lnkFrom = 0;
int32 flags = 0;
for (auto i = _blocks.cbegin(), e = _blocks.cend(); true; ++i) {
int blockLnkIndex = (i == e) ? 0 : (*i)->lnkIndex();
uint16 blockFrom = (i == e) ? _text.size() : (*i)->from();
int32 blockFlags = (i == e) ? 0 : (*i)->flags();
if (blockFlags != flags) {
if ((flags & TextBlockFItalic) && !(blockFlags & TextBlockFItalic)) { // write italic
result.push_back(EntityInText(EntityInTextItalic, italicStart, originalLength - italicStart));
} else if ((blockFlags & TextBlockFItalic) && !(flags & TextBlockFItalic)) {
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;
}
bool checkBlockFlags = (blockFrom >= selection.from) && (blockFrom <= selection.to);
if (checkBlockFlags && blockFlags != flags) {
flagsChangeCallback(flags, blockFlags);
flags = blockFlags;
}
if (blockLnkIndex && !_links.at(blockLnkIndex - 1)) { // ignore empty links
blockLnkIndex = 0;
}
if (blockLnkIndex != lnkIndex) {
int32 rangeFrom = lnkFrom, rangeTo = blockFrom;
if (lnkIndex && rangeTo > rangeFrom) { // write link
QStringRef r = _text.midRef(rangeFrom, rangeTo - rangeFrom);
if (auto entity = _links.at(lnkIndex - 1)->getEntityInText(lnkStart, r)) {
result.push_back(entity);
originalLength += entity.length();
} else {
originalLength += r.size();
if (lnkIndex) {
auto rangeFrom = qMax(selection.from, lnkFrom);
auto rangeTo = qMin(blockFrom, selection.to);
if (rangeTo > rangeFrom) { // handle click handler
QStringRef r = _text.midRef(rangeFrom, rangeTo - rangeFrom);
if (lnkFrom != rangeFrom || blockFrom != rangeTo) {
appendPartCallback(r);
} else {
clickHandlerFinishCallback(r, _links.at(lnkIndex - 1));
}
}
}
lnkIndex = blockLnkIndex;
if (lnkIndex) {
lnkFrom = blockFrom;
lnkStart = originalLength;
clickHandlerStartCallback();
}
}
if (i == e) break;
if (i == e || blockFrom >= selection.to) break;
TextBlockType type = (*i)->type();
if (type == TextBlockTSkip) continue;
if ((*i)->type() == TextBlockTSkip) continue;
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) {
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;
}

View File

@ -97,7 +97,7 @@ public:
int32 countHeight(int32 width) const;
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 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);
bool hasLinks() const;
@ -178,8 +178,9 @@ public:
int length() const {
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
if (_text.size() < maxdots) return false;
@ -207,6 +208,10 @@ public:
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);
// 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!
void textParseEntities(QString &text, int32 flags, EntitiesInText *inOutEntities, bool rich) {
EntitiesInText mono;
EntitiesInText result;
bool withHashtags = (flags & TextParseHashtags);
bool withMentions = (flags & TextParseMentions);
@ -1445,6 +1445,9 @@ void textParseEntities(QString &text, int32 flags, EntitiesInText *inOutEntities
bool withMono = (flags & TextParseMono);
if (withMono) { // parse mono entities (code and pre)
int existingEntityIndex = 0, existingEntitiesCount = inOutEntities->size();
int existingEntityShiftLeft = 0;
QString newText;
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);
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,
codeStart = mCode.hasMatch() ? mCode.capturedStart() : INT_MAX,
codeEnd = mCode.hasMatch() ? mCode.capturedEnd() : INT_MAX,
@ -1506,11 +1509,25 @@ void textParseEntities(QString &text, int32 flags, EntitiesInText *inOutEntities
continue;
}
if (newText.isEmpty()) newText.reserve(text.size());
bool addNewlineBefore = false, addNewlineAfter = false;
int32 outerStart = tagStart, outerEnd = tagEnd;
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) {
while (outerStart > 0 && chIsSpace(*(start + outerStart - 1), rich) && !chIsNewline(*(start + outerStart - 1))) {
--outerStart;
@ -1540,14 +1557,27 @@ void textParseEntities(QString &text, int32 flags, EntitiesInText *inOutEntities
}
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 (addNewlineBefore) newText.append('\n');
existingEntityShiftLeft += (innerStart - outerStart) - (addNewlineBefore ? 1 : 0);
int32 tagLength = innerEnd - innerStart;
mono.push_back(EntityInText(pre ? EntityInTextPre : EntityInTextCode, newText.size(), tagLength));
int entityStart = newText.size(), entityLength = innerEnd - innerStart;
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');
existingEntityShiftLeft += (outerEnd - innerEnd) - (addNewlineAfter ? 1 : 0);
offset = matchOffset = outerEnd;
}
@ -1555,8 +1585,19 @@ void textParseEntities(QString &text, int32 flags, EntitiesInText *inOutEntities
newText.append(start + offset, len - offset);
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();
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);
QRegularExpressionMatch mExplicitDomain = _reExplicitDomain.match(text, matchOffset);
QRegularExpressionMatch mHashtag = withHashtags ? _reHashtag.match(text, matchOffset) : QRegularExpressionMatch();
QRegularExpressionMatch mMention = withMentions ? _reMention.match(text, qMax(mentionSkip, matchOffset)) : QRegularExpressionMatch();
QRegularExpressionMatch mBotCommand = withBotCommands ? _reBotCommand.match(text, matchOffset) : QRegularExpressionMatch();
auto mDomain = _reDomain.match(text, matchOffset);
auto mExplicitDomain = _reExplicitDomain.match(text, matchOffset);
auto mHashtag = withHashtags ? _reHashtag.match(text, matchOffset) : QRegularExpressionMatch();
auto mMention = withMentions ? _reMention.match(text, qMax(mentionSkip, matchOffset)) : QRegularExpressionMatch();
auto mBotCommand = withBotCommands ? _reBotCommand.match(text, matchOffset) : QRegularExpressionMatch();
EntityInTextType lnkType = EntityInTextUrl;
int32 lnkStart = 0, lnkLength = 0;
@ -1735,19 +1776,23 @@ void textParseEntities(QString &text, int32 flags, EntitiesInText *inOutEntities
lnkLength = (p - start) - lnkStart;
}
}
for (; monoEntity < monoCount && mono[monoEntity].offset() <= lnkStart; ++monoEntity) {
monoTill = qMax(monoTill, mono[monoEntity].offset() + mono[monoEntity].length());
inOutEntities->push_back(mono[monoEntity]);
for (; existingEntityIndex < existingEntitiesCount && inOutEntities->at(existingEntityIndex).offset() <= lnkStart; ++existingEntityIndex) {
auto &entity = inOutEntities->at(existingEntityIndex);
accumulate_max(existingEntityEnd, entity.offset() + entity.length());
result.push_back(entity);
}
if (lnkStart >= monoTill) {
if (lnkStart >= existingEntityEnd) {
inOutEntities->push_back(EntityInText(lnkType, lnkStart, lnkLength));
}
offset = matchOffset = lnkStart + lnkLength;
}
for (; monoEntity < monoCount; ++monoEntity) {
monoTill = qMax(monoTill, mono[monoEntity].offset() + mono[monoEntity].length());
inOutEntities->push_back(mono[monoEntity]);
if (!result.isEmpty()) {
for (; existingEntityIndex < existingEntitiesCount; ++existingEntityIndex) {
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) {
if (_offset > textEnd) {
_offset = textEnd;
@ -108,7 +111,19 @@ private:
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
QString textClean(const QString &text);