diff --git a/Telegram/SourceFiles/app.cpp b/Telegram/SourceFiles/app.cpp index 1a31593ba..b99ceee01 100644 --- a/Telegram/SourceFiles/app.cpp +++ b/Telegram/SourceFiles/app.cpp @@ -79,6 +79,9 @@ namespace { typedef QMap RandomData; RandomData randomData; + typedef QMap SentTextData; + SentTextData sentTextData; + HistoryItem *hoveredItem = 0, *pressedItem = 0, *hoveredLinkItem = 0, *pressedLinkItem = 0, *contextItem = 0, *mousedItem = 0; QPixmap *sprite = 0, *emojis = 0, *emojisLarge = 0; @@ -1681,6 +1684,7 @@ namespace App { void historyClearItems() { historyClearMsgs(); randomData.clear(); + sentTextData.clear(); mutedPeers.clear(); updatedPeers.clear(); cSetSavedPeers(SavedPeers()); @@ -1756,6 +1760,18 @@ namespace App { return 0; } + void historyRegSentText(uint64 randomId, const QString &text) { + sentTextData.insert(randomId, text); + } + + void historyUnregSentText(uint64 randomId) { + sentTextData.remove(randomId); + } + + QString histSentTextByItem(uint64 randomId) { + return sentTextData.value(randomId); + } + void prepareCorners(RoundCorners index, int32 radius, const style::color &color, const style::color *shadow = 0, QImage *cors = 0) { int32 r = radius * cIntRetinaFactor(), s = st::msgShadow * cIntRetinaFactor(); QImage rect(r * 3, r * 3 + (shadow ? s : 0), QImage::Format_ARGB32_Premultiplied), localCors[4]; diff --git a/Telegram/SourceFiles/app.h b/Telegram/SourceFiles/app.h index a68c390c3..51995a5c5 100644 --- a/Telegram/SourceFiles/app.h +++ b/Telegram/SourceFiles/app.h @@ -188,6 +188,9 @@ namespace App { void historyRegRandom(uint64 randomId, MsgId itemId); void historyUnregRandom(uint64 randomId); MsgId histItemByRandom(uint64 randomId); + void historyRegSentText(uint64 itemId, const QString &text); + void historyUnregSentText(uint64 itemId); + QString histSentTextByItem(uint64 itemId); void hoveredItem(HistoryItem *item); HistoryItem *hoveredItem(); diff --git a/Telegram/SourceFiles/gui/emoji_config.h b/Telegram/SourceFiles/gui/emoji_config.h index d66229f6a..9a7a5de94 100644 --- a/Telegram/SourceFiles/gui/emoji_config.h +++ b/Telegram/SourceFiles/gui/emoji_config.h @@ -146,8 +146,8 @@ inline bool emojiEdge(const QChar *ch) { inline QString replaceEmojis(const QString &text) { QString result; - LinkRanges lnkRanges = textParseLinks(text, TextParseLinks | TextParseMentions | TextParseHashtags); - int32 currentLink = 0, lnkCount = lnkRanges.size(); + LinksInText links = textParseLinks(text, TextParseLinks | TextParseMentions | TextParseHashtags); + int32 currentLink = 0, lnkCount = links.size(); const QChar *emojiStart = text.unicode(), *emojiEnd = emojiStart, *e = text.cend(); bool canFindEmoji = true, consumePrevious = false; for (const QChar *ch = emojiEnd; ch != e;) { @@ -157,14 +157,14 @@ inline QString replaceEmojis(const QString &text) { emojiFind(ch, e, newEmojiEnd, emojiCode); } - while (currentLink < lnkCount && ch >= lnkRanges[currentLink].from + lnkRanges[currentLink].len) { + while (currentLink < lnkCount && ch >= emojiStart + links[currentLink].offset + links[currentLink].length) { ++currentLink; } EmojiPtr emoji = emojiCode ? emojiGet(emojiCode) : 0; if (emoji && emoji != TwoSymbolEmoji && (ch == emojiStart || !ch->isLetterOrNumber() || !(ch - 1)->isLetterOrNumber()) && (newEmojiEnd == e || !newEmojiEnd->isLetterOrNumber() || newEmojiEnd == emojiStart || !(newEmojiEnd - 1)->isLetterOrNumber()) && - (currentLink >= lnkCount || (ch < lnkRanges[currentLink].from && newEmojiEnd <= lnkRanges[currentLink].from) || (ch >= lnkRanges[currentLink].from + lnkRanges[currentLink].len && newEmojiEnd > lnkRanges[currentLink].from + lnkRanges[currentLink].len)) + (currentLink >= lnkCount || (ch < emojiStart + links[currentLink].offset && newEmojiEnd <= emojiStart + links[currentLink].offset) || (ch >= emojiStart + links[currentLink].offset + links[currentLink].length && newEmojiEnd > emojiStart + links[currentLink].offset + links[currentLink].length)) ) { // if (newEmojiEnd < e && newEmojiEnd->unicode() == ' ') ++newEmojiEnd; if (result.isEmpty()) result.reserve(text.size()); diff --git a/Telegram/SourceFiles/gui/text.cpp b/Telegram/SourceFiles/gui/text.cpp index 5bda612d5..9d3e3355c 100644 --- a/Telegram/SourceFiles/gui/text.cpp +++ b/Telegram/SourceFiles/gui/text.cpp @@ -328,13 +328,13 @@ public: } bool checkWaitedLink() { - if (waitingLink == linksEnd || ptr < waitingLink->from || links.size() >= 0x7FFF) { + if (waitingLink == linksEnd || ptr < start + waitingLink->offset || links.size() >= 0x7FFF) { return true; } createBlock(); - QString lnkUrl = QString(waitingLink->from, waitingLink->len), lnkText; + QString lnkUrl = QString(start + waitingLink->offset, waitingLink->length), lnkText; int32 fullDisplayed; getLinkData(lnkUrl, lnkText, fullDisplayed); @@ -342,7 +342,7 @@ public: lnkIndex = 0x8000 + links.size(); _t->_text += lnkText; - ptr = waitingLink->from + waitingLink->len; + ptr = start + waitingLink->offset + waitingLink->length; createBlock(); ++waitingLink; @@ -519,8 +519,31 @@ public: emoji = e; } - TextParser(Text *t, const QString &text, const TextParseOptions &options) : _t(t), src(text), - rich(options.flags & TextParseRichText), multiline(options.flags & TextParseMultiline), maxLnkIndex(0), flags(0), lnkIndex(0), stopAfterWidth(QFIXED_MAX) { + TextParser(Text *t, const QString &text, const TextParseOptions &options) : _t(t), + src(text), + rich(options.flags & TextParseRichText), + multiline(options.flags & TextParseMultiline), + maxLnkIndex(0), + flags(0), + lnkIndex(0), + stopAfterWidth(QFIXED_MAX) { + if (options.flags & TextParseLinks) { + lnkRanges = textParseLinks(src, options.flags, rich); + } + parse(options); + } + TextParser(Text *t, const QString &text, const LinksInText &links, const TextParseOptions &options) : _t(t), + src(text), + rich(options.flags & TextParseRichText), + multiline(options.flags & TextParseMultiline), + maxLnkIndex(0), + flags(0), + lnkIndex(0), + stopAfterWidth(QFIXED_MAX) { + lnkRanges = links; + parse(options); + } + void parse(const TextParseOptions &options) { int flags = options.flags; if (options.maxw > 0 && options.maxh > 0) { stopAfterWidth = ((options.maxh / _t->_font->height) + 1) * options.maxw; @@ -529,10 +552,6 @@ public: start = src.constData(); end = start + src.size(); - if (options.flags & TextParseLinks) { - lnkRanges = textParseLinks(src, options.flags, rich); - } - while (start != end && chIsTrimmed(*start, rich)) { ++start; } @@ -552,8 +571,8 @@ public: ch = chInt = 0; lastSkipped = false; lastSpace = true; - waitingLink = lnkRanges.isEmpty() ? 0 : lnkRanges.constData(); - linksEnd = lnkRanges.isEmpty() ? 0 : waitingLink + lnkRanges.size(); + waitingLink = lnkRanges.cbegin(); + linksEnd = lnkRanges.cend(); for (ptr = start; ptr <= end; ++ptr) { if (!checkWaitedLink()) { break; @@ -615,8 +634,8 @@ private: const QChar *start, *end, *ptr; bool rich, multiline; - LinkRanges lnkRanges; - const LinkRange *waitingLink, *linksEnd; + LinksInText lnkRanges; + LinksInText::const_iterator waitingLink, linksEnd; struct TextLinkData { TextLinkData(const QString &url = QString(), int32 fullDisplayed = 1) : url(url), fullDisplayed(fullDisplayed) { @@ -2305,6 +2324,7 @@ void Text::setText(style::font font, const QString &text, const TextParseOptions void Text::recountNaturalSize(bool initial, Qt::LayoutDirection optionsDir) { NewlineBlock *lastNewline = 0; + _maxWidth = _minHeight = 0; int32 lineHeight = 0; int32 result = 0, lastNewlineStart = 0; QFixed _width = 0, last_rBearing = 0, last_rPadding = 0; @@ -2369,8 +2389,14 @@ void Text::recountNaturalSize(bool initial, Qt::LayoutDirection optionsDir) { } } -void Text::setMarkedText(style::font font, const QString &text, const LinksInText &links) { - +void Text::setMarkedText(style::font font, const QString &text, const LinksInText &links, const TextParseOptions &options) { + if (!_textStyle) _initDefault(); + _font = font; + clean(); + { + TextParser parser(this, text, links, options); + } + recountNaturalSize(true, options.dir); } void Text::setRichText(style::font font, const QString &text, TextParseOptions options, const TextCustomTagsMap &custom) { @@ -2460,16 +2486,80 @@ bool Text::hasLinks() const { return !_links.isEmpty(); } -void Text::setSkipBlock(int32 width) { - +void Text::setSkipBlock(int32 width, int32 height) { + if (!_blocks.isEmpty() && _blocks.back()->type() == TextBlockSkip) { + SkipBlock *block = static_cast(_blocks.back()); + if (block->width() == width && block->height() == height) return; + _text.resize(block->from()); + _blocks.pop_back(); + } + _text.push_back('_'); + _blocks.push_back(new SkipBlock(_font, _text, _text.size() - 1, width, height, 0)); + recountNaturalSize(false); } void Text::removeSkipBlock() { - + if (!_blocks.isEmpty() && _blocks.back()->type() == TextBlockSkip) { + _text.resize(_blocks.back()->from()); + _blocks.pop_back(); + recountNaturalSize(false); + } } LinksInText Text::calcLinksInText() const { - return LinksInText(); + LinksInText result; + int32 lnkFrom = 0, lnkIndex = 0, offset = 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 != lnkIndex) { + if (lnkIndex) { // write link + const TextLinkPtr &lnk(_links.at(lnkIndex - 1)); + const QString &url(lnk ? lnk->text() : QString()); + + int32 rangeFrom = lnkFrom, rangeTo = blockFrom; + if (rangeTo > rangeFrom) { + QStringRef r = _text.midRef(rangeFrom, rangeTo - rangeFrom); + if (url.isEmpty()) { + offset += r.size(); + } else { + QUrl u(url); + if (r.size() <= 3 || _text.midRef(lnkFrom, r.size() - 3) == (u.isValid() ? u.toDisplayString() : url).midRef(0, r.size() - 3)) { // same link + if (url.at(0) == '@') { + result.push_back(LinkInText(LinkInTextMention, offset, url.size())); + } else if (url.at(0) == '#') { + result.push_back(LinkInText(LinkInTextHashtag, offset, url.size())); + } else if (url.at(0) == '/') { + result.push_back(LinkInText(LinkInTextBotCommand, offset, url.size())); + } else if (url.indexOf('@') > 0 && url.indexOf('/') <= 0) { + result.push_back(LinkInText(LinkInTextEmail, offset, url.size())); + } else { + result.push_back(LinkInText(LinkInTextUrl, offset, url.size())); + } + offset += url.size(); + } else { + result.push_back(LinkInText(LinkInTextCustomUrl, offset, r.size(), url)); + offset += r.size(); + } + } + } + } + lnkIndex = blockLnkIndex; + lnkFrom = blockFrom; + } + if (i == e) break; + + TextBlockType type = (*i)->type(); + if (type == TextBlockSkip) continue; + + if (!blockLnkIndex) { + int32 rangeFrom = (*i)->from(), rangeTo = uint16((*i)->from() + TextPainter::_blockLength(this, i, e)); + if (rangeTo > rangeFrom) { + offset += rangeTo - rangeFrom; + } + } + } + return result; } int32 Text::countHeight(int32 w) const { @@ -4058,16 +4148,16 @@ QString textSearchKey(const QString &text) { bool textSplit(QString &sendingText, QString &leftText, int32 limit) { if (leftText.isEmpty() || !limit) return false; - LinkRanges lnkRanges = textParseLinks(leftText, TextParseLinks | TextParseMentions | TextParseHashtags); - int32 currentLink = 0, lnkCount = lnkRanges.size(); + LinksInText links = textParseLinks(leftText, TextParseLinks | TextParseMentions | TextParseHashtags); + int32 currentLink = 0, lnkCount = links.size(); int32 s = 0, half = limit / 2, goodLevel = 0; for (const QChar *start = leftText.constData(), *ch = start, *end = leftText.constEnd(), *good = ch; ch != end; ++ch, ++s) { - while (currentLink < lnkCount && ch >= lnkRanges[currentLink].from + lnkRanges[currentLink].len) { + while (currentLink < lnkCount && ch >= start + links[currentLink].offset + links[currentLink].length) { ++currentLink; } - bool inLink = (currentLink < lnkCount) && (ch > lnkRanges[currentLink].from) && (ch < lnkRanges[currentLink].from + lnkRanges[currentLink].len); + bool inLink = (currentLink < lnkCount) && (ch > start + links[currentLink].offset) && (ch < start + links[currentLink].offset + links[currentLink].length); if (s > half) { if (inLink) { if (!goodLevel) good = ch; @@ -4125,8 +4215,8 @@ bool textSplit(QString &sendingText, QString &leftText, int32 limit) { return true; } -LinkRanges textParseLinks(const QString &text, int32 flags, bool rich) { // some code is duplicated in flattextarea.cpp! - LinkRanges lnkRanges; +LinksInText textParseLinks(const QString &text, int32 flags, bool rich) { // some code is duplicated in flattextarea.cpp! + LinksInText result; bool withHashtags = (flags & TextParseHashtags); bool withMentions = (flags & TextParseMentions); @@ -4149,7 +4239,8 @@ LinkRanges textParseLinks(const QString &text, int32 flags, bool rich) { // some QRegularExpressionMatch mMention = withMentions ? _reMention.match(text, qMax(mentionSkip, matchOffset)) : QRegularExpressionMatch(); QRegularExpressionMatch mBotCommand = withBotCommands ? _reBotCommand.match(text, matchOffset) : QRegularExpressionMatch(); - LinkRange link; + LinkInTextType lnkType = LinkInTextUrl; + int32 lnkOffset = 0, lnkLength = 0; int32 domainOffset = mDomain.hasMatch() ? mDomain.capturedStart() : INT_MAX, domainEnd = mDomain.hasMatch() ? mDomain.capturedEnd() : INT_MAX, explicitDomainOffset = mExplicitDomain.hasMatch() ? mExplicitDomain.capturedStart() : INT_MAX, @@ -4214,9 +4305,9 @@ LinkRanges textParseLinks(const QString &text, int32 flags, bool rich) { // some continue; } } - - link.from = start + mentionOffset; - link.len = start + mentionEnd - link.from; + lnkType = LinkInTextMention; + lnkOffset = mentionOffset; + lnkLength = mentionEnd - mentionOffset; } else if (hashtagOffset < domainOffset && hashtagOffset < botCommandOffset) { if (hashtagOffset > nextCmd) { const QChar *after = textSkipCommand(start + nextCmd, start + len); @@ -4226,8 +4317,9 @@ LinkRanges textParseLinks(const QString &text, int32 flags, bool rich) { // some } } - link.from = start + hashtagOffset; - link.len = start + hashtagEnd - link.from; + lnkType = LinkInTextHashtag; + lnkOffset = hashtagOffset; + lnkLength = hashtagEnd - hashtagOffset; } else if (botCommandOffset < domainOffset) { if (botCommandOffset > nextCmd) { const QChar *after = textSkipCommand(start + nextCmd, start + len); @@ -4237,8 +4329,9 @@ LinkRanges textParseLinks(const QString &text, int32 flags, bool rich) { // some } } - link.from = start + botCommandOffset; - link.len = start + botCommandEnd - link.from; + lnkType = LinkInTextBotCommand; + lnkOffset = botCommandOffset; + lnkLength = botCommandEnd - botCommandOffset; } else { if (domainOffset > nextCmd) { const QChar *after = textSkipCommand(start + nextCmd, start + len); @@ -4262,16 +4355,17 @@ LinkRanges textParseLinks(const QString &text, int32 flags, bool rich) { // some if (mailOffset < offset) { mailOffset = offset; } - link.from = start + mailOffset; - link.len = domainEnd - mailOffset; + lnkType = LinkInTextEmail; + lnkOffset = mailOffset; + lnkLength = domainEnd - mailOffset; } } - if (!link.from || !link.len) { + if (lnkType == LinkInTextUrl && !lnkLength) { if (!isProtocolValid || !isTopDomainValid) { matchOffset = domainEnd; continue; } - link.from = start + domainOffset; + lnkOffset = domainOffset; QStack parenth; const QChar *domainEnd = start + mDomain.capturedEnd(), *p = domainEnd; @@ -4306,15 +4400,15 @@ LinkRanges textParseLinks(const QString &text, int32 flags, bool rich) { // some continue; } } - link.len = p - link.from; + lnkLength = (p - start) - lnkOffset; } } - lnkRanges.push_back(link); + result.push_back(LinkInText(lnkType, lnkOffset, lnkLength)); - offset = matchOffset = (link.from - start) + link.len; + offset = matchOffset = lnkOffset + lnkLength; } - return lnkRanges; + return result; } void emojiDraw(QPainter &p, EmojiPtr e, int x, int y) { diff --git a/Telegram/SourceFiles/gui/text.h b/Telegram/SourceFiles/gui/text.h index 6d165bdbd..30304541c 100644 --- a/Telegram/SourceFiles/gui/text.h +++ b/Telegram/SourceFiles/gui/text.h @@ -39,14 +39,57 @@ enum { TextInstagramHashtags = 0x200, }; -struct LinkRange { - LinkRange() : from(0), len(0) { - } - const QChar *from; - int32 len; +enum LinkInTextType { + LinkInTextUrl, + LinkInTextCustomUrl, + LinkInTextEmail, + LinkInTextHashtag, + LinkInTextMention, + LinkInTextBotCommand, }; -typedef QVector LinkRanges; -LinkRanges textParseLinks(const QString &text, int32 flags, bool rich = false); +struct LinkInText { + LinkInText(LinkInTextType type, int32 offset, int32 length, const QString &text = QString()) : type(type), offset(offset), length(length), text(text) { + } + LinkInTextType type; + int32 offset, length; + QString text; +}; +typedef QList LinksInText; +inline LinksInText linksFromMTP(const QVector &entities) { + LinksInText result; + if (!entities.isEmpty()) { + result.reserve(entities.size()); + for (int32 i = 0, l = entities.size(); i != l; ++i) { + const MTPMessageEntity &e(entities.at(i)); + switch (e.type()) { + case mtpc_messageEntityUrl: { const MTPDmessageEntityUrl &d(e.c_messageEntityUrl()); result.push_back(LinkInText(LinkInTextUrl, d.voffset.v, d.vlength.v)); } break; + case mtpc_messageEntityTextUrl: { const MTPDmessageEntityTextUrl &d(e.c_messageEntityTextUrl()); result.push_back(LinkInText(LinkInTextCustomUrl, d.voffset.v, d.vlength.v, textClean(qs(d.vurl)))); } break; + case mtpc_messageEntityEmail: { const MTPDmessageEntityEmail &d(e.c_messageEntityEmail()); result.push_back(LinkInText(LinkInTextEmail, d.voffset.v, d.vlength.v)); } break; + case mtpc_messageEntityHashtag: { const MTPDmessageEntityHashtag &d(e.c_messageEntityHashtag()); result.push_back(LinkInText(LinkInTextHashtag, d.voffset.v, d.vlength.v)); } break; + case mtpc_messageEntityMention: { const MTPDmessageEntityMention &d(e.c_messageEntityMention()); result.push_back(LinkInText(LinkInTextMention, d.voffset.v, d.vlength.v)); } break; + case mtpc_messageEntityBotCommand: { const MTPDmessageEntityBotCommand &d(e.c_messageEntityBotCommand()); result.push_back(LinkInText(LinkInTextBotCommand, d.voffset.v, d.vlength.v)); } break; + } + } + } + return result; +} +inline MTPVector linksToMTP(const LinksInText &links) { + MTPVector result(MTP_vector(0)); + QVector &v(result._vector().v); + for (int32 i = 0, s = links.size(); i != s; ++i) { + const LinkInText &l(links.at(i)); + switch (l.type) { + case LinkInTextUrl: v.push_back(MTP_messageEntityUrl(MTP_int(l.offset), MTP_int(l.length))); break; + case LinkInTextCustomUrl: v.push_back(MTP_messageEntityTextUrl(MTP_int(l.offset), MTP_int(l.length), MTP_string(l.text))); break; + case LinkInTextEmail: v.push_back(MTP_messageEntityEmail(MTP_int(l.offset), MTP_int(l.length))); break; + case LinkInTextHashtag: v.push_back(MTP_messageEntityHashtag(MTP_int(l.offset), MTP_int(l.length))); break; + case LinkInTextMention: v.push_back(MTP_messageEntityMention(MTP_int(l.offset), MTP_int(l.length))); break; + case LinkInTextBotCommand: v.push_back(MTP_messageEntityBotCommand(MTP_int(l.offset), MTP_int(l.length))); break; + } + } + return result; +} +LinksInText textParseLinks(const QString &text, int32 flags, bool rich = false); #include "gui/emoji_config.h" @@ -464,23 +507,6 @@ enum TextSelectType { typedef QPair TextCustomTag; // open str and close str typedef QMap TextCustomTagsMap; -enum LinkInTextType { - LinkInTextUrl, - LinkInTextCustomUrl, - LinkInTextEmail, - LinkInTextHashtag, - LinkInTextMention, - LinkInTextBotCommand, -}; -struct LinkInText { - LinkInText(LinkInTextType type, int32 offset, int32 length, const QString &text = QString()) : type(type), offset(offset), length(length), text(text) { - } - LinkInTextType type; - int32 offset, length; - QString text; -}; -typedef QList LinksInText; - class Text { public: @@ -492,7 +518,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 LinksInText &links); + void setMarkedText(style::font font, const QString &text, const LinksInText &links, const TextParseOptions &options = _defaultOptions); void setLink(uint16 lnkIndex, const TextLinkPtr &lnk); bool hasLinks() const; @@ -500,7 +526,7 @@ public: bool hasSkipBlock() const { return _blocks.isEmpty() ? false : _blocks.back()->type() == TextBlockSkip; } - void setSkipBlock(int32 width); + void setSkipBlock(int32 width, int32 height); void removeSkipBlock(); LinksInText calcLinksInText() const; diff --git a/Telegram/SourceFiles/history.cpp b/Telegram/SourceFiles/history.cpp index e19699468..f3ae1a3c0 100644 --- a/Telegram/SourceFiles/history.cpp +++ b/Telegram/SourceFiles/history.cpp @@ -110,15 +110,17 @@ namespace { return item ? item->toHistoryForwarded() : 0; } inline const TextParseOptions &itemTextParseOptions(HistoryItem *item) { - History *h = item->history(); - UserData *f = item->from(); - if ((!h->peer->chat && h->peer->asUser()->botInfo) || (!f->chat && f->asUser()->botInfo) || (h->peer->chat && h->peer->asChat()->botStatus >= 0)) { - return _historyBotOptions; - } - return _historyTextOptions; + return itemTextParseOptions(item->history(), item->from()); } } +const TextParseOptions &itemTextParseOptions(History *h, UserData *f) { + if ((!h->peer->chat && h->peer->asUser()->botInfo) || (!f->chat && f->asUser()->botInfo) || (h->peer->chat && h->peer->asChat()->botStatus >= 0)) { + return _historyBotOptions; + } + return _historyTextOptions; +} + void historyInit() { _initTextOptions(); } @@ -1035,7 +1037,7 @@ void History::addToFront(const QVector &slice) { int32 addToH = 0, skip = 0; if (!isEmpty()) { - addToH = -front()->height; + if (width) addToH = -front()->height; pop_front(); // remove date block } HistoryItem *till = isEmpty() ? 0 : front()->front(), *prev = 0; @@ -1049,12 +1051,16 @@ void History::addToFront(const QVector &slice) { if (prev && prev->date.date() != adding->date.date()) { HistoryItem *dayItem = createDayServiceMsg(this, block, adding->date); block->push_back(dayItem); - dayItem->y = block->height; - block->height += dayItem->resize(width); + if (width) { + dayItem->y = block->height; + block->height += dayItem->resize(width); + } } block->push_back(adding); - adding->y = block->height; - block->height += adding->resize(width); + if (width) { + adding->y = block->height; + block->height += adding->resize(width); + } setMsgCount(msgCount + 1); prev = adding; } @@ -1063,8 +1069,10 @@ void History::addToFront(const QVector &slice) { if (till && prev && prev->date.date() != till->date.date()) { HistoryItem *dayItem = createDayServiceMsg(this, block, till->date); block->push_back(dayItem); - dayItem->y = block->height; - block->height += dayItem->resize(width); + if (width) { + dayItem->y = block->height; + block->height += dayItem->resize(width); + } } if (block->size()) { if (loadedAtBottom() && wasMsgCount < unreadCount && msgCount >= unreadCount) { @@ -1079,8 +1087,10 @@ void History::addToFront(const QVector &slice) { } } push_front(block); - addToH += block->height; - ++skip; + if (width) { + addToH += block->height; + ++skip; + } if (loadedAtBottom()) { // add photos to overview and authors to lastAuthors int32 mask = 0; @@ -1167,16 +1177,18 @@ void History::addToFront(const QVector &slice) { HistoryBlock *dateBlock = new HistoryBlock(this); HistoryItem *dayItem = createDayServiceMsg(this, dateBlock, front()->front()->date); dateBlock->push_back(dayItem); - int32 dh = dayItem->resize(width); - dateBlock->height = dh; - if (skip) { - front()->y += dh; + if (width) { + int32 dh = dayItem->resize(width); + dateBlock->height = dh; + if (skip) { + front()->y += dh; + } + addToH += dh; + ++skip; } push_front(dateBlock); // date block - addToH += dh; - ++skip; } - if (addToH) { + if (width && addToH) { for (iterator i = begin(), e = end(); i != e; ++i) { if (skip) { --skip; @@ -5026,7 +5038,7 @@ HistoryMessage::HistoryMessage(History *history, HistoryBlock *block, const MTPD QString text(textClean(qs(msg.vmessage))); initTime(); initMedia(msg.vmedia, text); - initDimensions(text, msg.has_entities() ? linksFromMTP(msg.ventities.c_vector().v) : LinksInText()); + setText(text, msg.has_entities() ? linksFromMTP(msg.ventities.c_vector().v) : LinksInText()); } HistoryMessage::HistoryMessage(History *history, HistoryBlock *block, MsgId msgId, int32 flags, QDateTime date, int32 from, const QString &msg, const LinksInText &links, const MTPMessageMedia &media) : @@ -5039,7 +5051,7 @@ HistoryItem(history, block, msgId, flags, date, from) QString text(msg); initTime(); initMedia(media, text); - initDimensions(text, links); + setText(text, links); } HistoryMessage::HistoryMessage(History *history, HistoryBlock *block, MsgId msgId, int32 flags, QDateTime date, int32 from, const QString &msg, const LinksInText &links, HistoryMedia *fromMedia) : @@ -5054,7 +5066,7 @@ HistoryItem(history, block, msgId, flags, date, from) _media = fromMedia->clone(); _media->regItem(this); } - initDimensions(msg, links); + setText(msg, links); } HistoryMessage::HistoryMessage(History *history, HistoryBlock *block, MsgId msgId, int32 flags, QDateTime date, int32 from, DocumentData *doc) : @@ -5066,7 +5078,7 @@ HistoryItem(history, block, msgId, flags, date, from) { initTime(); initMediaFromDocument(doc); - initDimensions(QString(), LinksInText()); + setText(QString(), LinksInText()); } void HistoryMessage::initTime() { @@ -5154,22 +5166,6 @@ void HistoryMessage::initMediaFromDocument(DocumentData *doc) { _media->regItem(this); } -void HistoryMessage::initDimensions(const QString &text, const LinksInText &links) { - if (!_media || !text.isEmpty()) { // !justMedia() - if (_media && _media->isDisplayed()) { - _text.setMarkedText(st::msgFont, text, links, itemTextParseOptions(this)); - } else { - _text.setMarkedText(st::msgFont, text + textcmdSkipBlock(timeWidth(true), st::msgDateFont->height - st::msgDateDelta.y()), links, itemTextParseOptions(this)); - } - for (int32 i = 0, l = links.size(); i != l; ++i) { - if (links.at(i).type == LinkInTextUrl || links.at(i).type == LinkInTextCustomUrl || links.at(i).type == LinkInTextEmail) { - _flags |= MTPDmessage_flag_HAS_TEXT_LINKS; - break; - } - } - } -} - void HistoryMessage::initDimensions(const HistoryItem *parent) { if (justMedia()) { _media->initDimensions(this); @@ -5182,19 +5178,13 @@ void HistoryMessage::initDimensions(const HistoryItem *parent) { if (_media) { _media->initDimensions(this); if (_media->isDisplayed() && _text.hasSkipBlock()) { - QString was = HistoryMessage::selectedText(FullItemSel); - if (!was.isEmpty()) { - _text.setText(st::msgFont, was, itemTextParseOptions(this)); // without date skip - _textWidth = 0; - _textHeight = 0; - } + _text.removeSkipBlock(); + _textWidth = 0; + _textHeight = 0; } else if (!_media->isDisplayed() && !_text.hasSkipBlock()) { - QString was = HistoryMessage::selectedText(FullItemSel); - if (!was.isEmpty()) { - _text.setText(st::msgFont, was + textcmdSkipBlock(timeWidth(true), st::msgDateFont->height - st::msgDateDelta.y()), itemTextParseOptions(this)); // without date skip - _textWidth = 0; - _textHeight = 0; - } + _text.setSkipBlock(timeWidth(true), st::msgDateFont->height - st::msgDateDelta.y()); + _textWidth = 0; + _textHeight = 0; } if (_media->isDisplayed()) { int32 maxw = _media->maxWidth() + st::msgPadding.left() + st::msgPadding.right(); @@ -5226,6 +5216,10 @@ QString HistoryMessage::selectedText(uint32 selection) const { return _text.original(selectedFrom, selectedTo); } +LinksInText HistoryMessage::textLinks() const { + return _text.calcLinksInText(); +} + QString HistoryMessage::inDialogsText() const { QString result = _media ? _media->inDialogsText() : QString(); return result.isEmpty() ? _text.original(0, 0xFFFF, false) : result; @@ -5237,6 +5231,7 @@ HistoryMedia *HistoryMessage::getMedia(bool inOverview) const { void HistoryMessage::setMedia(const MTPmessageMedia &media) { if ((!_media || _media->isImageLink()) && media.type() == mtpc_messageMediaEmpty) return; + bool mediaWasDisplayed = false; if (_media) { mediaWasDisplayed = _media->isDisplayed(); @@ -5246,24 +5241,38 @@ void HistoryMessage::setMedia(const MTPmessageMedia &media) { QString t; initMedia(media, t); if (_media && _media->isDisplayed() && !mediaWasDisplayed) { - QString was = HistoryMessage::selectedText(FullItemSel); - if (!was.isEmpty()) { - _text.setText(st::msgFont, was, itemTextParseOptions(this)); // without date skip - _textWidth = 0; - _textHeight = 0; - } + _text.removeSkipBlock(); + _textWidth = 0; + _textHeight = 0; } else if (mediaWasDisplayed && (!_media || !_media->isDisplayed())) { - QString was = HistoryMessage::selectedText(FullItemSel); - if (!was.isEmpty()) { - _text.setText(st::msgFont, was + textcmdSkipBlock(timeWidth(true), st::msgDateFont->height - st::msgDateDelta.y()), itemTextParseOptions(this)); // without date skip - _textWidth = 0; - _textHeight = 0; - } + _text.setSkipBlock(timeWidth(true), st::msgDateFont->height - st::msgDateDelta.y()); + _textWidth = 0; + _textHeight = 0; } initDimensions(0); if (App::main()) App::main()->itemResized(this); } +void HistoryMessage::setText(const QString &text, const LinksInText &links) { + if (!_media || !text.isEmpty()) { // !justMedia() + if (_media && _media->isDisplayed()) { + _text.setMarkedText(st::msgFont, text, links, itemTextParseOptions(this)); + } else { + _text.setMarkedText(st::msgFont, text + textcmdSkipBlock(timeWidth(true), st::msgDateFont->height - st::msgDateDelta.y()), links, itemTextParseOptions(this)); + } + if (id > 0) { + for (int32 i = 0, l = links.size(); i != l; ++i) { + if (links.at(i).type == LinkInTextUrl || links.at(i).type == LinkInTextCustomUrl || links.at(i).type == LinkInTextEmail) { + _flags |= MTPDmessage_flag_HAS_TEXT_LINKS; + break; + } + } + } + _textWidth = 0; + _textHeight = 0; + } +} + void HistoryMessage::draw(QPainter &p, uint32 selection) const { textstyleSet(&(out() ? st::outTextStyle : st::inTextStyle)); @@ -5368,6 +5377,8 @@ void HistoryMessage::drawMessageText(QPainter &p, const QRect &trect, uint32 sel } int32 HistoryMessage::resize(int32 width, bool dontRecountText, const HistoryItem *parent) { + if (width < st::msgMinWidth) return _height; + width -= st::msgMargin.left() + st::msgMargin.right(); if (justMedia()) { _height = _media->resize(width, dontRecountText, this); @@ -5573,7 +5584,7 @@ HistoryMessage::~HistoryMessage() { } } -HistoryForwarded::HistoryForwarded(History *history, HistoryBlock *block, const MTPDmessage &msg) : HistoryMessage(history, block, msg.vid.v, msg.vflags.v, ::date(msg.vdate), msg.vfrom_id.v, textClean(qs(msg.vmessage)), msg.vmedia) +HistoryForwarded::HistoryForwarded(History *history, HistoryBlock *block, const MTPDmessage &msg) : HistoryMessage(history, block, msg.vid.v, msg.vflags.v, ::date(msg.vdate), msg.vfrom_id.v, textClean(qs(msg.vmessage)), msg.has_entities() ? linksFromMTP(msg.ventities.c_vector().v) : LinksInText(), msg.vmedia) , fwdDate(::date(msg.vfwd_date)) , fwdFrom(App::user(msg.vfwd_from_id.v)) , fwdFromVersion(fwdFrom->nameVersion) @@ -5582,7 +5593,7 @@ HistoryForwarded::HistoryForwarded(History *history, HistoryBlock *block, const fwdNameUpdated(); } -HistoryForwarded::HistoryForwarded(History *history, HistoryBlock *block, MsgId id, HistoryMessage *msg) : HistoryMessage(history, block, id, ((history->peer->input.type() != mtpc_inputPeerSelf) ? (MTPDmessage_flag_out | MTPDmessage_flag_unread) : 0) | (msg->getMedia() && (msg->getMedia()->type() == MediaTypeAudio/* || msg->getMedia()->type() == MediaTypeVideo*/) ? MTPDmessage_flag_media_unread : 0), ::date(unixtime()), MTP::authedId(), msg->justMedia() ? QString() : msg->HistoryMessage::selectedText(FullItemSel), msg->getMedia()) +HistoryForwarded::HistoryForwarded(History *history, HistoryBlock *block, MsgId id, HistoryMessage *msg) : HistoryMessage(history, block, id, ((history->peer->input.type() != mtpc_inputPeerSelf) ? (MTPDmessage_flag_out | MTPDmessage_flag_unread) : 0) | (msg->getMedia() && (msg->getMedia()->type() == MediaTypeAudio/* || msg->getMedia()->type() == MediaTypeVideo*/) ? MTPDmessage_flag_media_unread : 0), ::date(unixtime()), MTP::authedId(), msg->justMedia() ? QString() : msg->HistoryMessage::selectedText(FullItemSel), msg->HistoryMessage::textLinks(), msg->getMedia()) , fwdDate(msg->dateForwarded()) , fwdFrom(msg->fromForwarded()) , fwdFromVersion(fwdFrom->nameVersion) @@ -5769,7 +5780,7 @@ void HistoryForwarded::getSymbol(uint16 &symbol, bool &after, bool &upon, int32 return HistoryMessage::getSymbol(symbol, after, upon, x, y); } -HistoryReply::HistoryReply(History *history, HistoryBlock *block, const MTPDmessage &msg) : HistoryMessage(history, block, msg.vid.v, msg.vflags.v, ::date(msg.vdate), msg.vfrom_id.v, textClean(qs(msg.vmessage)), msg.vmedia) +HistoryReply::HistoryReply(History *history, HistoryBlock *block, const MTPDmessage &msg) : HistoryMessage(history, block, msg.vid.v, msg.vflags.v, ::date(msg.vdate), msg.vfrom_id.v, textClean(qs(msg.vmessage)), msg.has_entities() ? linksFromMTP(msg.ventities.c_vector().v) : LinksInText(), msg.vmedia) , replyToMsgId(msg.vreply_to_msg_id.v) , replyToMsg(0) , replyToVersion(0) diff --git a/Telegram/SourceFiles/history.h b/Telegram/SourceFiles/history.h index e07994c8e..bcdcc1e3b 100644 --- a/Telegram/SourceFiles/history.h +++ b/Telegram/SourceFiles/history.h @@ -780,6 +780,8 @@ public: } virtual void setMedia(const MTPmessageMedia &media) { } + virtual void setText(const QString &text, const LinksInText &links) { + } virtual QString time() const { return QString(); } @@ -1288,7 +1290,6 @@ public: void initMediaFromText(QString ¤tText); void initMediaFromDocument(DocumentData *doc); void initDimensions(const HistoryItem *parent = 0); - void initDimensions(const QString &text, const LinksInText &links); void fromNameUpdated() const; bool justMedia() const { @@ -1322,9 +1323,11 @@ public: } QString selectedText(uint32 selection) const; + LinksInText textLinks() const; QString inDialogsText() const; HistoryMedia *getMedia(bool inOverview = false) const; void setMedia(const MTPmessageMedia &media); + void setText(const QString &text, const LinksInText &links); QString time() const { return _time; @@ -1559,3 +1562,5 @@ protected: QString text; bool freezed; }; + +const TextParseOptions &itemTextParseOptions(History *h, UserData *f); diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index 3a75d23cb..e9ff8f248 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -982,6 +982,12 @@ DialogsIndexed &MainWidget::dialogsList() { return dialogs.dialogsList(); } +QString cleanMessage(const QString &text) { + QString result = text.trimmed(); + // clean bad symbols + return result; +} + void MainWidget::sendPreparedText(History *hist, const QString &text, MsgId replyTo, WebPageId webPageId) { saveRecentHashtags(text); QString sendingText, leftText = text; @@ -990,7 +996,10 @@ void MainWidget::sendPreparedText(History *hist, const QString &text, MsgId repl MsgId newId = clientMsgId(); uint64 randomId = MTP::nonce(); + sendingText = cleanMessage(sendingText); + App::historyRegRandom(randomId, newId); + App::historyRegSentText(randomId, sendingText); MTPstring msgText(MTP_string(sendingText)); int32 flags = newMessageFlags(hist->peer); // unread, out @@ -1006,8 +1015,9 @@ void MainWidget::sendPreparedText(History *hist, const QString &text, MsgId repl WebPageData *page = App::webPage(webPageId); media = MTP_messageMediaWebPage(MTP_webPagePending(MTP_long(page->id), MTP_int(page->pendingTill))); } + MTPVector localEntities = linksToMTP(textParseLinks(sendingText, itemTextParseOptions(hist, App::self()).flags)); hist->addToBack(MTP_message(MTP_int(flags), MTP_int(newId), MTP_int(MTP::authedId()), App::peerToMTP(hist->peer->id), MTPint(), MTPint(), MTP_int(replyTo), MTP_int(unixtime()), msgText, media, MTPnullMarkup, MTPnullEntities)); - hist->sendRequestId = MTP::send(MTPmessages_SendMessage(MTP_int(sendFlags), hist->peer->input, MTP_int(replyTo), msgText, MTP_long(randomId), MTPnullMarkup, MTPnullEntities), App::main()->rpcDone(&MainWidget::sentDataReceived, randomId), RPCFailHandlerPtr(), 0, 0, hist->sendRequestId); + hist->sendRequestId = MTP::send(MTPmessages_SendMessage(MTP_int(sendFlags), hist->peer->input, MTP_int(replyTo), msgText, MTP_long(randomId), MTPnullMarkup, localEntities), App::main()->rpcDone(&MainWidget::sentDataReceived, randomId), RPCFailHandlerPtr(), 0, 0, hist->sendRequestId); } finishForwarding(hist); @@ -2221,8 +2231,21 @@ void MainWidget::sentDataReceived(uint64 randomId, const MTPmessages_SentMessage switch (result.type()) { case mtpc_messages_sentMessage: { const MTPDmessages_sentMessage &d(result.c_messages_sentMessage()); - - if (randomId) feedUpdate(MTP_updateMessageID(d.vid, MTP_long(randomId))); // ignore real date + + HistoryItem *item = 0; + if (randomId) { + QString text = App::histSentTextByItem(randomId); + feedUpdate(MTP_updateMessageID(d.vid, MTP_long(randomId))); // ignore real date + LinksInText links(linksFromMTP(d.ventities.c_vector().v)); + if (!text.isEmpty() && !links.isEmpty()) { + item = App::histItemById(d.vid.v); + if (item) { + item->setText(text, links); + item->initDimensions(0); + itemResized(item); + } + } + } if (updInited) { if (!updPtsUpdated(d.vpts.v, d.vpts_count.v)) { @@ -2231,7 +2254,9 @@ void MainWidget::sentDataReceived(uint64 randomId, const MTPmessages_SentMessage } } - HistoryItem *item = App::histItemById(d.vid.v); + if (!item) { + item = App::histItemById(d.vid.v); + } if (item) { item->setMedia(d.vmedia); } @@ -2240,7 +2265,20 @@ void MainWidget::sentDataReceived(uint64 randomId, const MTPmessages_SentMessage case mtpc_messages_sentMessageLink: { const MTPDmessages_sentMessageLink &d(result.c_messages_sentMessageLink()); - if (randomId) feedUpdate(MTP_updateMessageID(d.vid, MTP_long(randomId))); // ignore real date + HistoryItem *item = 0; + if (randomId) { + //QString text = App::histSentTextByItem(randomId); + feedUpdate(MTP_updateMessageID(d.vid, MTP_long(randomId))); // ignore real date + //LinksInText links(linksFromMTP(d.ventities.c_vector().v)); + //if (!text.isEmpty() && !links.isEmpty()) { + // item = App::histItemById(d.vid.v); + // if (item) { + // item->setText(text, links); + // item->initDimensions(0); + // itemResized(item); + // } + //} + } if (updInited) { if (!updPtsUpdated(d.vpts.v, d.vpts_count.v)) { @@ -2250,11 +2288,11 @@ void MainWidget::sentDataReceived(uint64 randomId, const MTPmessages_SentMessage } App::feedUserLinks(d.vlinks); - if (d.vmedia.type() != mtpc_messageMediaEmpty) { - HistoryItem *item = App::histItemById(d.vid.v); - if (item) { - item->setMedia(d.vmedia); - } + if (!item) { + item = App::histItemById(d.vid.v); + } + if (item) { + item->setMedia(d.vmedia); } } break; }; @@ -3434,6 +3472,7 @@ void MainWidget::feedUpdate(const MTPUpdate &update) { } App::historyUnregRandom(d.vrandom_id.v); } + App::historyUnregSentText(d.vrandom_id.v); } break; case mtpc_updateReadMessagesContents: {