From c4a7d48d96e248f192f6eb3f6f4d8bf9b4302e7b Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 13 Apr 2016 21:29:32 +0300 Subject: [PATCH] Selection of text in attachments: captions, web page previews. Text/HistoryItem/HistoryMedia::getState() unified. Text::getStateElided added. Tested with web page previews only. --- Telegram/SourceFiles/boxes/confirmbox.cpp | 4 +- Telegram/SourceFiles/dropdown.cpp | 2 +- Telegram/SourceFiles/history.cpp | 532 +++++++++--------- Telegram/SourceFiles/history.h | 179 ++++-- Telegram/SourceFiles/historywidget.cpp | 204 +++---- Telegram/SourceFiles/historywidget.h | 2 +- .../inline_bot_layout_internal.cpp | 14 +- .../inline_bots/inline_bot_layout_internal.h | 14 +- .../inline_bots/inline_bot_layout_item.h | 2 +- Telegram/SourceFiles/layout.h | 2 +- Telegram/SourceFiles/mediaview.cpp | 7 +- .../SourceFiles/overview/overview_layout.cpp | 18 +- .../SourceFiles/overview/overview_layout.h | 14 +- Telegram/SourceFiles/overviewwidget.cpp | 14 +- Telegram/SourceFiles/overviewwidget.h | 4 +- Telegram/SourceFiles/profilewidget.cpp | 4 +- Telegram/SourceFiles/ui/flatlabel.cpp | 6 +- Telegram/SourceFiles/ui/text.cpp | 371 ++++++------ Telegram/SourceFiles/ui/text.h | 96 +++- 19 files changed, 815 insertions(+), 674 deletions(-) diff --git a/Telegram/SourceFiles/boxes/confirmbox.cpp b/Telegram/SourceFiles/boxes/confirmbox.cpp index 3f3f73edc..910243ea0 100644 --- a/Telegram/SourceFiles/boxes/confirmbox.cpp +++ b/Telegram/SourceFiles/boxes/confirmbox.cpp @@ -119,10 +119,10 @@ void ConfirmBox::updateHover() { QPoint m(mapFromGlobal(_lastMousePos)); textstyleSet(&st::boxTextStyle); - ClickHandlerPtr handler = _text.linkLeft(m.x() - st::boxPadding.left(), m.y() - st::boxPadding.top(), _textWidth, width(), style::al_left); + auto state = _text.getStateLeft(m.x() - st::boxPadding.left(), m.y() - st::boxPadding.top(), _textWidth, width()); textstyleRestore(); - ClickHandler::setActive(handler, this); + ClickHandler::setActive(state.link, this); } void ConfirmBox::closePressed() { diff --git a/Telegram/SourceFiles/dropdown.cpp b/Telegram/SourceFiles/dropdown.cpp index 93ce0f929..676ff0025 100644 --- a/Telegram/SourceFiles/dropdown.cpp +++ b/Telegram/SourceFiles/dropdown.cpp @@ -1349,7 +1349,7 @@ void StickerPanInner::paintInlineItems(Painter &p, const QRect &r) { int w = item->width(); if (left + w > fromx) { p.translate(left, top); - item->paint(p, r.translated(-left, -top), 0, &context); + item->paint(p, r.translated(-left, -top), &context); p.translate(-left, -top); } left += w; diff --git a/Telegram/SourceFiles/history.cpp b/Telegram/SourceFiles/history.cpp index 4c90a95b5..930d7c65e 100644 --- a/Telegram/SourceFiles/history.cpp +++ b/Telegram/SourceFiles/history.cpp @@ -2687,10 +2687,9 @@ void ReplyKeyboard::paint(Painter &p, const QRect &clip) const { } } -void ReplyKeyboard::getState(ClickHandlerPtr &lnk, int x, int y) const { +ClickHandlerPtr ReplyKeyboard::getState(int x, int y) const { t_assert(_width > 0); - lnk.clear(); for_const (const ButtonRow &row, _rows) { for_const (const Button &button, row) { QRect rect(button.rect); @@ -2699,11 +2698,11 @@ void ReplyKeyboard::getState(ClickHandlerPtr &lnk, int x, int y) const { if (rect.x() + rect.width() > _width) break; if (rect.contains(x, y)) { - lnk = button.link; - return; + return button.link; } } } + return ClickHandlerPtr(); } void ReplyKeyboard::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) { @@ -3464,10 +3463,11 @@ int HistoryPhoto::resizeGetHeight(int width) { return _height; } -void HistoryPhoto::draw(Painter &p, const QRect &r, bool selected, uint64 ms) const { +void HistoryPhoto::draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const { if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return; _data->automaticLoad(_parent); + bool selected = (selection == FullSelection); bool loaded = _data->loaded(), displayLoading = _data->displayLoading(); bool notChild = (_parent->getMedia() == this); @@ -3561,12 +3561,14 @@ void HistoryPhoto::draw(Painter &p, const QRect &r, bool selected, uint64 ms) co } } else { p.setPen(st::black); - _caption.draw(p, st::msgPadding.left(), skipy + height + st::mediaPadding.bottom() + st::mediaCaptionSkip, captionw); + _caption.draw(p, st::msgPadding.left(), skipy + height + st::mediaPadding.bottom() + st::mediaCaptionSkip, captionw, style::al_left, 0, -1, selection); } } -void HistoryPhoto::getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int x, int y) const { - if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return; +HistoryTextState HistoryPhoto::getState(int x, int y, HistoryStateRequest request) const { + HistoryTextState result; + + if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return result; int skipx = 0, skipy = 0, width = _width, height = _height; bool bubble = _parent->hasBubble(); @@ -3577,10 +3579,8 @@ void HistoryPhoto::getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int int captionw = width - st::msgPadding.left() - st::msgPadding.right(); height -= _caption.countHeight(captionw) + st::msgPadding.bottom(); if (x >= st::msgPadding.left() && y >= height && x < st::msgPadding.left() + captionw && y < _height) { - bool inText = false; - _caption.getState(lnk, inText, x - st::msgPadding.left(), y - height, captionw); - state = inText ? HistoryInTextCursorState : HistoryDefaultCursorState; - return; + result = _caption.getState(x - st::msgPadding.left(), y - height, captionw, request.forText()); + return result; } height -= st::mediaCaptionSkip; } @@ -3589,26 +3589,27 @@ void HistoryPhoto::getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int } if (x >= skipx && y >= skipy && x < skipx + width && y < skipy + height) { if (_data->uploading()) { - lnk = _cancell; + result.link = _cancell; } else if (_data->loaded()) { - lnk = _openl; + result.link = _openl; } else if (_data->loading()) { DelayedStorageImage *delayed = _data->full->toDelayedStorageImage(); if (!delayed || !delayed->location().isNull()) { - lnk = _cancell; + result.link = _cancell; } } else { - lnk = _savel; + result.link = _savel; } if (_caption.isEmpty() && _parent->getMedia() == this) { int32 fullRight = skipx + width, fullBottom = skipy + height; bool inDate = _parent->pointInTime(fullRight, fullBottom, x, y, InfoDisplayOverImage); if (inDate) { - state = HistoryInDateCursorState; + result.cursor = HistoryInDateCursorState; } } - return; + return result; } + return result; } void HistoryPhoto::updateSentMedia(const MTPMessageMedia &media) { @@ -3681,11 +3682,11 @@ void HistoryPhoto::detachFromParent() { } const QString HistoryPhoto::inDialogsText() const { - return _caption.isEmpty() ? lang(lng_in_dlg_photo) : _caption.original(0, 0xFFFF, Text::ExpandLinksNone); + return _caption.isEmpty() ? lang(lng_in_dlg_photo) : _caption.original(AllTextSelection, Text::ExpandLinksNone); } const QString HistoryPhoto::inHistoryText() const { - return qsl("[ ") + lang(lng_in_dlg_photo) + (_caption.isEmpty() ? QString() : (qsl(", ") + _caption.original(0, 0xFFFF, Text::ExpandLinksAll))) + qsl(" ]"); + return qsl("[ ") + lang(lng_in_dlg_photo) + (_caption.isEmpty() ? QString() : (qsl(", ") + _caption.original(AllTextSelection, Text::ExpandLinksAll))) + qsl(" ]"); } ImagePtr HistoryPhoto::replyPreview() { @@ -3788,11 +3789,12 @@ int HistoryVideo::resizeGetHeight(int width) { return _height; } -void HistoryVideo::draw(Painter &p, const QRect &r, bool selected, uint64 ms) const { +void HistoryVideo::draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const { if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return; _data->automaticLoad(_parent); bool loaded = _data->loaded(), displayLoading = _data->displayLoading(); + bool selected = (selection == FullSelection); int skipx = 0, skipy = 0, width = _width, height = _height; bool bubble = _parent->hasBubble(); @@ -3879,12 +3881,14 @@ void HistoryVideo::draw(Painter &p, const QRect &r, bool selected, uint64 ms) co } } else { p.setPen(st::black); - _caption.draw(p, st::msgPadding.left(), skipy + height + st::mediaPadding.bottom() + st::mediaCaptionSkip, captionw); + _caption.draw(p, st::msgPadding.left(), skipy + height + st::mediaPadding.bottom() + st::mediaCaptionSkip, captionw, style::al_left, 0, -1, selection); } } -void HistoryVideo::getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int x, int y) const { - if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return; +HistoryTextState HistoryVideo::getState(int x, int y, HistoryStateRequest request) const { + HistoryTextState result; + + if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return result; bool loaded = _data->loaded(); @@ -3898,9 +3902,7 @@ void HistoryVideo::getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int int32 captionw = width - st::msgPadding.left() - st::msgPadding.right(); height -= _caption.countHeight(captionw) + st::msgPadding.bottom(); if (x >= st::msgPadding.left() && y >= height && x < st::msgPadding.left() + captionw && y < _height) { - bool inText = false; - _caption.getState(lnk, inText, x - st::msgPadding.left(), y - height, captionw); - state = inText ? HistoryInTextCursorState : HistoryDefaultCursorState; + result = _caption.getState(x - st::msgPadding.left(), y - height, captionw); } height -= st::mediaCaptionSkip; } @@ -3908,16 +3910,17 @@ void HistoryVideo::getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int height -= skipy + st::mediaPadding.bottom(); } if (x >= skipx && y >= skipy && x < skipx + width && y < skipy + height) { - lnk = loaded ? _openl : (_data->loading() ? _cancell : _savel); + result.link = loaded ? _openl : (_data->loading() ? _cancell : _savel); if (_caption.isEmpty() && _parent->getMedia() == this) { int32 fullRight = skipx + width, fullBottom = skipy + height; bool inDate = _parent->pointInTime(fullRight, fullBottom, x, y, InfoDisplayOverImage); if (inDate) { - state = HistoryInDateCursorState; + result.cursor = HistoryInDateCursorState; } } - return; + return result; } + return result; } void HistoryVideo::setStatusSize(int32 newSize) const { @@ -3925,11 +3928,11 @@ void HistoryVideo::setStatusSize(int32 newSize) const { } const QString HistoryVideo::inDialogsText() const { - return _caption.isEmpty() ? lang(lng_in_dlg_video) : _caption.original(0, 0xFFFF, Text::ExpandLinksNone); + return _caption.isEmpty() ? lang(lng_in_dlg_video) : _caption.original(AllTextSelection, Text::ExpandLinksNone); } const QString HistoryVideo::inHistoryText() const { - return qsl("[ ") + lang(lng_in_dlg_video) + (_caption.isEmpty() ? QString() : (qsl(", ") + _caption.original(0, 0xFFFF, Text::ExpandLinksAll))) + qsl(" ]"); + return qsl("[ ") + lang(lng_in_dlg_video) + (_caption.isEmpty() ? QString() : (qsl(", ") + _caption.original(AllTextSelection, Text::ExpandLinksAll))) + qsl(" ]"); } void HistoryVideo::updateStatusText() const { @@ -4126,11 +4129,12 @@ int HistoryDocument::resizeGetHeight(int width) { return _height; } -void HistoryDocument::draw(Painter &p, const QRect &r, bool selected, uint64 ms) const { +void HistoryDocument::draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const { if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return; _data->automaticLoad(_parent); bool loaded = _data->loaded(), displayLoading = _data->displayLoading(); + bool selected = (selection == FullSelection); int captionw = _width - st::msgPadding.left() - st::msgPadding.right(); @@ -4342,12 +4346,14 @@ void HistoryDocument::draw(Painter &p, const QRect &r, bool selected, uint64 ms) if (auto captioned = Get()) { p.setPen(st::black); - captioned->_caption.draw(p, st::msgPadding.left(), bottom, captionw); + captioned->_caption.draw(p, st::msgPadding.left(), bottom, captionw, style::al_left, 0, -1, selection); } } -void HistoryDocument::getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int x, int y) const { - if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return; +HistoryTextState HistoryDocument::getState(int x, int y, HistoryStateRequest request) const { + HistoryTextState result; + + if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return result; bool out = _parent->out(), isPost = _parent->isPost(), outbg = out && !isPost; bool loaded = _data->loaded(); @@ -4363,14 +4369,14 @@ void HistoryDocument::getState(ClickHandlerPtr &lnk, HistoryCursorState &state, QRect rthumb(rtlrect(st::msgFileThumbPadding.left(), st::msgFileThumbPadding.top(), st::msgFileThumbSize, st::msgFileThumbSize, _width)); if ((_data->loading() || _data->uploading() || !loaded) && rthumb.contains(x, y)) { - lnk = (_data->loading() || _data->uploading()) ? _cancell : _savel; - return; + result.link = (_data->loading() || _data->uploading()) ? _cancell : _savel; + return result; } if (_data->status != FileUploadFailed) { if (rtlrect(nameleft, linktop, thumbed->_linkw, st::semiboldFont->height, _width).contains(x, y)) { - lnk = (_data->loading() || _data->uploading()) ? thumbed->_linkcancell : thumbed->_linksavel; - return; + result.link = (_data->loading() || _data->uploading()) ? thumbed->_linkcancell : thumbed->_linksavel; + return result; } } } else { @@ -4378,25 +4384,24 @@ void HistoryDocument::getState(ClickHandlerPtr &lnk, HistoryCursorState &state, QRect inner(rtlrect(st::msgFilePadding.left(), st::msgFilePadding.top(), st::msgFileSize, st::msgFileSize, _width)); if ((_data->loading() || _data->uploading() || !loaded) && inner.contains(x, y)) { - lnk = (_data->loading() || _data->uploading()) ? _cancell : _savel; - return; + result.link = (_data->loading() || _data->uploading()) ? _cancell : _savel; + return result; } } int32 height = _height; if (auto captioned = Get()) { if (y >= bottom) { - bool inText = false; - captioned->_caption.getState(lnk, inText, x - st::msgPadding.left(), y - bottom, _width - st::msgPadding.left() - st::msgPadding.right()); - state = inText ? HistoryInTextCursorState : HistoryDefaultCursorState; - return; + result = captioned->_caption.getState(x - st::msgPadding.left(), y - bottom, _width - st::msgPadding.left() - st::msgPadding.right(), request.forText()); + return result; } height -= captioned->_caption.countHeight(_width - st::msgPadding.left() - st::msgPadding.right()) + st::msgPadding.bottom(); } if (x >= 0 && y >= 0 && x < _width && y < height && !_data->loading() && !_data->uploading() && _data->isValid()) { - lnk = _openl; - return; + result.link = _openl; + return result; } + return result; } const QString HistoryDocument::inDialogsText() const { @@ -4414,7 +4419,7 @@ const QString HistoryDocument::inDialogsText() const { } if (auto captioned = Get()) { if (!captioned->_caption.isEmpty()) { - result.append(' ').append(captioned->_caption.original(0, 0xFFFF, Text::ExpandLinksNone)); + result.append(' ').append(captioned->_caption.original(AllTextSelection, Text::ExpandLinksNone)); } } return result; @@ -4436,7 +4441,7 @@ const QString HistoryDocument::inHistoryText() const { } if (auto captioned = Get()) { if (!captioned->_caption.isEmpty()) { - result.append(qsl(", ")).append(captioned->_caption.original(0, 0xFFFF, Text::ExpandLinksAll)); + result.append(qsl(", ")).append(captioned->_caption.original(AllTextSelection, Text::ExpandLinksAll)); } } return qsl("[ ") + result.append(qsl(" ]")); @@ -4719,11 +4724,13 @@ int HistoryGif::resizeGetHeight(int width) { return _height; } -void HistoryGif::draw(Painter &p, const QRect &r, bool selected, uint64 ms) const { +void HistoryGif::draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const { if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return; _data->automaticLoad(_parent); bool loaded = _data->loaded(), displayLoading = (_parent->id < 0) || _data->displayLoading(); + bool selected = (selection == FullSelection); + if (loaded && !gif() && _gif != BadClipReader && cAutoPlayGif()) { Ui::autoplayMediaInlineAsync(_parent->fullId()); } @@ -4824,15 +4831,17 @@ void HistoryGif::draw(Painter &p, const QRect &r, bool selected, uint64 ms) cons if (!_caption.isEmpty()) { p.setPen(st::black); - _caption.draw(p, st::msgPadding.left(), skipy + height + st::mediaPadding.bottom() + st::mediaCaptionSkip, captionw); + _caption.draw(p, st::msgPadding.left(), skipy + height + st::mediaPadding.bottom() + st::mediaCaptionSkip, captionw, style::al_left, 0, -1, selection); } else if (_parent->getMedia() == this && (_data->uploading() || App::hoveredItem() == _parent)) { int32 fullRight = skipx + width, fullBottom = skipy + height; _parent->drawInfo(p, fullRight, fullBottom, 2 * skipx + width, selected, InfoDisplayOverImage); } } -void HistoryGif::getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int x, int y) const { - if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return; +HistoryTextState HistoryGif::getState(int x, int y, HistoryStateRequest request) const { + HistoryTextState result; + + if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return result; int32 skipx = 0, skipy = 0, width = _width, height = _height; bool bubble = _parent->hasBubble(); @@ -4843,10 +4852,8 @@ void HistoryGif::getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int x int32 captionw = width - st::msgPadding.left() - st::msgPadding.right(); height -= _caption.countHeight(captionw) + st::msgPadding.bottom(); if (x >= st::msgPadding.left() && y >= height && x < st::msgPadding.left() + captionw && y < _height) { - bool inText = false; - _caption.getState(lnk, inText, x - st::msgPadding.left(), y - height, captionw); - state = inText ? HistoryInTextCursorState : HistoryDefaultCursorState; - return; + result = _caption.getState(x - st::msgPadding.left(), y - height, captionw, request.forText()); + return result; } height -= st::mediaCaptionSkip; } @@ -4855,27 +4862,28 @@ void HistoryGif::getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int x } if (x >= skipx && y >= skipy && x < skipx + width && y < skipy + height) { if (_data->uploading()) { - lnk = _cancell; + result.link = _cancell; } else if (!gif() || !cAutoPlayGif()) { - lnk = _data->loaded() ? _openl : (_data->loading() ? _cancell : _savel); + result.link = _data->loaded() ? _openl : (_data->loading() ? _cancell : _savel); } if (_parent->getMedia() == this) { int32 fullRight = skipx + width, fullBottom = skipy + height; bool inDate = _parent->pointInTime(fullRight, fullBottom, x, y, InfoDisplayOverImage); if (inDate) { - state = HistoryInDateCursorState; + result.cursor = HistoryInDateCursorState; } } - return; + return result; } + return result; } const QString HistoryGif::inDialogsText() const { - return qsl("GIF") + (_caption.isEmpty() ? QString() : (' ' + _caption.original(0, 0xFFFF, Text::ExpandLinksNone))); + return qsl("GIF") + (_caption.isEmpty() ? QString() : (' ' + _caption.original(AllTextSelection, Text::ExpandLinksNone))); } const QString HistoryGif::inHistoryText() const { - return qsl("[ GIF ") + (_caption.isEmpty() ? QString() : (_caption.original(0, 0xFFFF, Text::ExpandLinksAll) + ' ')) + qsl(" ]"); + return qsl("[ GIF ") + (_caption.isEmpty() ? QString() : (_caption.original(AllTextSelection, Text::ExpandLinksAll) + ' ')) + qsl(" ]"); } void HistoryGif::setStatusSize(int32 newSize) const { @@ -5048,11 +5056,12 @@ int HistorySticker::resizeGetHeight(int width) { // return new height return _height; } -void HistorySticker::draw(Painter &p, const QRect &r, bool selected, uint64 ms) const { +void HistorySticker::draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const { if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return; _data->checkSticker(); bool loaded = _data->loaded(); + bool selected = (selection == FullSelection); bool out = _parent->out(), isPost = _parent->isPost(), childmedia = (_parent->getMedia() != this); @@ -5120,8 +5129,9 @@ void HistorySticker::draw(Painter &p, const QRect &r, bool selected, uint64 ms) } } -void HistorySticker::getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int x, int y) const { - if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return; +HistoryTextState HistorySticker::getState(int x, int y, HistoryStateRequest request) const { + HistoryTextState result; + if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return result; bool out = _parent->out(), isPost = _parent->isPost(), childmedia = (_parent->getMedia() != this); @@ -5156,8 +5166,8 @@ void HistorySticker::getState(ClickHandlerPtr &lnk, HistoryCursorState &state, i if (via) { int viah = st::msgReplyPadding.top() + st::msgServiceNameFont->height + (reply ? 0 : st::msgReplyPadding.bottom()); if (x >= rectx && y >= recty && x < rectx + rectw && y < recty + viah) { - lnk = via->_lnk; - return; + result.link = via->_lnk; + return result; } int skip = st::msgServiceNameFont->height + (reply ? 2 * st::msgReplyPadding.top() : 0); recty += skip; @@ -5165,23 +5175,24 @@ void HistorySticker::getState(ClickHandlerPtr &lnk, HistoryCursorState &state, i } if (reply) { if (x >= rectx && y >= recty && x < rectx + rectw && y < recty + recth) { - lnk = reply->replyToLink(); - return; + result.link = reply->replyToLink(); + return result; } } } if (_parent->getMedia() == this) { bool inDate = _parent->pointInTime(usex + usew, _height, x, y, InfoDisplayOverImage); if (inDate) { - state = HistoryInDateCursorState; + result.cursor = HistoryInDateCursorState; } } int pixLeft = usex + (usew - _pixw) / 2, pixTop = (_minh - _pixh) / 2; if (x >= pixLeft && x < pixLeft + _pixw && y >= pixTop && y < pixTop + _pixh) { - lnk = _packLink; - return; + result.link = _packLink; + return result; } + return result; } const QString HistorySticker::inDialogsText() const { @@ -5292,11 +5303,12 @@ void HistoryContact::initDimensions() { _height = _minh; } -void HistoryContact::draw(Painter &p, const QRect &r, bool selected, uint64 ms) const { +void HistoryContact::draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const { if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return; int32 skipx = 0, skipy = 0, width = _width, height = _height; bool out = _parent->out(), isPost = _parent->isPost(), outbg = out && !isPost; + bool selected = (selection == FullSelection); if (width >= _maxw) { width = _maxw; @@ -5345,7 +5357,8 @@ void HistoryContact::draw(Painter &p, const QRect &r, bool selected, uint64 ms) p.drawTextLeft(nameleft, statustop, width, _phone); } -void HistoryContact::getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int x, int y) const { +HistoryTextState HistoryContact::getState(int x, int y, HistoryStateRequest request) const { + HistoryTextState result; bool out = _parent->out(), isPost = _parent->isPost(), outbg = out && !isPost; int32 nameleft = 0, nametop = 0, nameright = 0, statustop = 0, linktop = 0; @@ -5353,14 +5366,15 @@ void HistoryContact::getState(ClickHandlerPtr &lnk, HistoryCursorState &state, i nameleft = st::msgFileThumbPadding.left() + st::msgFileThumbSize + st::msgFileThumbPadding.right(); linktop = st::msgFileThumbLinkTop; if (rtlrect(nameleft, linktop, _linkw, st::semiboldFont->height, _width).contains(x, y)) { - lnk = _linkl; - return; + result.link = _linkl; + return result; } } if (x >= 0 && y >= 0 && x < _width && y < _height && _contact) { - lnk = _contact->openLink(); - return; + result.link = _contact->openLink(); + return result; } + return result; } const QString HistoryContact::inDialogsText() const { @@ -5670,11 +5684,12 @@ int HistoryWebPage::resizeGetHeight(int width) { return _height; } -void HistoryWebPage::draw(Painter &p, const QRect &r, bool selected, uint64 ms) const { +void HistoryWebPage::draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const { if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return; int32 skipx = 0, skipy = 0, width = _width, height = _height; bool out = _parent->out(), isPost = _parent->isPost(), outbg = out && !isPost; + bool selected = (selection == FullSelection); style::color barfg = (selected ? (outbg ? st::msgOutReplyBarSelColor : st::msgInReplyBarSelColor) : (outbg ? st::msgOutReplyBarColor : st::msgInReplyBarColor)); style::color semibold = (selected ? (outbg ? st::msgOutServiceFgSelected : st::msgInServiceFgSelected) : (outbg ? st::msgOutServiceFg : st::msgInServiceFg)); @@ -5726,7 +5741,7 @@ void HistoryWebPage::draw(Painter &p, const QRect &r, bool selected, uint64 ms) if (_title.hasSkipBlock()) { endskip = _parent->skipBlockWidth(); } - _title.drawLeftElided(p, lshift, tshift, width, _width, _titleLines, style::al_left, 0, -1, endskip); + _title.drawLeftElided(p, lshift, tshift, width, _width, _titleLines, style::al_left, 0, -1, endskip, false, selection); tshift += _titleLines * _lineHeight; } if (_descriptionLines) { @@ -5735,7 +5750,7 @@ void HistoryWebPage::draw(Painter &p, const QRect &r, bool selected, uint64 ms) if (_description.hasSkipBlock()) { endskip = _parent->skipBlockWidth(); } - _description.drawLeftElided(p, lshift, tshift, width, _width, _descriptionLines, style::al_left, 0, -1, endskip); + _description.drawLeftElided(p, lshift, tshift, width, _width, _descriptionLines, style::al_left, 0, -1, endskip, false, toDescriptionSelection(selection)); tshift += _descriptionLines * _lineHeight; } if (_attach) { @@ -5747,7 +5762,8 @@ void HistoryWebPage::draw(Painter &p, const QRect &r, bool selected, uint64 ms) p.save(); p.translate(attachLeft, attachTop); - _attach->draw(p, r.translated(-attachLeft, -attachTop), selected, ms); + auto attachSelection = selected ? FullSelection : TextSelection{ 0, 0 }; + _attach->draw(p, r.translated(-attachLeft, -attachTop), attachSelection, ms); int32 pixwidth = _attach->currentWidth(), pixheight = _attach->height(); if (_data->type == WebPageVideo) { @@ -5774,8 +5790,10 @@ void HistoryWebPage::draw(Painter &p, const QRect &r, bool selected, uint64 ms) } } -void HistoryWebPage::getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int x, int y) const { - if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return; +HistoryTextState HistoryWebPage::getState(int x, int y, HistoryStateRequest request) const { + HistoryTextState result; + + if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return result; int32 skipx = 0, skipy = 0, width = _width, height = _height; int32 lshift = st::msgPadding.left() + st::webPageLeft, rshift = st::msgPadding.right(), bshift = st::msgPadding.bottom(); @@ -5785,48 +5803,74 @@ void HistoryWebPage::getState(ClickHandlerPtr &lnk, HistoryCursorState &state, i bshift += st::msgDateFont->height; } + bool inThumb = false; if (_asArticle) { int32 pw = qMax(_pixw, int16(_lineHeight)); if (rtlrect(lshift + width - pw, 0, pw, _pixh, _width).contains(x, y)) { - lnk = _openl; - return; + inThumb = true; } width -= pw + st::webPagePhotoDelta; } - int32 tshift = 0; + int tshift = 0, symbolAdd = 0; if (_siteNameWidth) { tshift += _lineHeight; } if (_titleLines) { + if (y >= tshift && y < tshift + _titleLines * _lineHeight) { + Text::StateRequestElided titleRequest = request.forText(); + titleRequest.lines = _titleLines; + result = _title.getStateElidedLeft(x - lshift, y - tshift, width, _width, titleRequest); + } else if (y >= tshift + _titleLines * _lineHeight) { + symbolAdd += _title.length(); + } tshift += _titleLines * _lineHeight; } if (_descriptionLines) { if (y >= tshift && y < tshift + _descriptionLines * _lineHeight) { - bool inText = false; - _description.getStateLeft(lnk, inText, x - lshift, y - tshift, width, _width); - state = inText ? HistoryInTextCursorState : HistoryDefaultCursorState; - return; + Text::StateRequestElided descriptionRequest = request.forText(); + descriptionRequest.lines = _descriptionLines; + result = _description.getStateElidedLeft(x - lshift, y - tshift, width, _width, descriptionRequest); + } else if (y >= tshift + _descriptionLines * _lineHeight) { + symbolAdd += _description.length(); } tshift += _descriptionLines * _lineHeight; } - if (_attach) { + if (inThumb) { + result.link = _openl; + } else if (_attach) { if (tshift) tshift += st::webPagePhotoSkip; if (x >= lshift && x < lshift + width && y >= tshift && y < _height - st::msgPadding.bottom()) { int32 attachLeft = lshift - bubble.left(), attachTop = tshift - bubble.top(); if (rtl()) attachLeft = _width - attachLeft - _attach->currentWidth(); - _attach->getState(lnk, state, x - attachLeft, y - attachTop); - if (lnk && !_data->document && _data->photo) { + result = _attach->getState(x - attachLeft, y - attachTop, request); + + if (result.link && !_data->document && _data->photo) { if (_data->type == WebPageProfile || _data->type == WebPageVideo) { - lnk = _openl; + result.link = _openl; } else if (_data->type == WebPagePhoto || _data->siteName == qstr("Twitter") || _data->siteName == qstr("Facebook")) { // leave photo link } else { - lnk = _openl; + result.link = _openl; } } } } + + result.symbol += symbolAdd; + return result; +} + +TextSelection HistoryWebPage::adjustSelection(TextSelection selection, TextSelectType type) const { + if (!_descriptionLines || selection.to <= _title.length()) { + return _title.adjustSelection(selection, type); + } + auto descriptionSelection = _description.adjustSelection(toDescriptionSelection(selection), type); + if (selection.from >= _title.length()) { + return fromDescriptionSelection(descriptionSelection); + } + auto titleSelection = _title.adjustSelection(selection, type); + return { titleSelection.from, fromDescriptionSelection(descriptionSelection).to }; } void HistoryWebPage::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) { @@ -6152,11 +6196,12 @@ int HistoryLocation::resizeGetHeight(int width) { return _height; } -void HistoryLocation::draw(Painter &p, const QRect &r, bool selected, uint64 ms) const { +void HistoryLocation::draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const { if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return; int32 skipx = 0, skipy = 0, width = _width, height = _height; bool bubble = _parent->hasBubble(); bool out = _parent->out(), isPost = _parent->isPost(), outbg = out && !isPost; + bool selected = (selection == FullSelection); if (bubble) { skipx = st::mediaPadding.left(); @@ -6173,11 +6218,11 @@ void HistoryLocation::draw(Painter &p, const QRect &r, bool selected, uint64 ms) p.setPen(st::black); if (!_title.isEmpty()) { - _title.drawLeftElided(p, skipx + st::msgPadding.left(), skipy, textw, _width, 2); + _title.drawLeftElided(p, skipx + st::msgPadding.left(), skipy, textw, _width, 2, style::al_left, 0, -1, 0, false, selection); skipy += qMin(_title.countHeight(textw), 2 * st::webPageTitleFont->height); } if (!_description.isEmpty()) { - _description.drawLeftElided(p, skipx + st::msgPadding.left(), skipy, textw, _width, 3); + _description.drawLeftElided(p, skipx + st::msgPadding.left(), skipy, textw, _width, 3, style::al_left, 0, -1, 0, false, toDescriptionSelection(selection)); skipy += qMin(_description.countHeight(textw), 3 * st::webPageDescriptionFont->height); } if (!_title.isEmpty() || !_description.isEmpty()) { @@ -6216,8 +6261,10 @@ void HistoryLocation::draw(Painter &p, const QRect &r, bool selected, uint64 ms) } } -void HistoryLocation::getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int x, int y) const { - if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return; +HistoryTextState HistoryLocation::getState(int x, int y, HistoryStateRequest request) const { + HistoryTextState result; + + if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return result; int32 skipx = 0, skipy = 0, width = _width, height = _height; bool bubble = _parent->hasBubble(); @@ -6235,10 +6282,21 @@ void HistoryLocation::getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int32 textw = _width - st::msgPadding.left() - st::msgPadding.right(); if (!_title.isEmpty()) { - skipy += qMin(_title.countHeight(textw), 2 * st::webPageTitleFont->height); + auto titleh = qMin(_title.countHeight(textw), 2 * st::webPageTitleFont->height); + if (y >= skipy && y < skipy + titleh) { + result = _title.getStateLeft(x - skipx - st::msgPadding.left(), y - skipy, textw, _width, request.forText()); + return result; + } + skipy += titleh; } if (!_description.isEmpty()) { - skipy += qMin(_description.countHeight(textw), 3 * st::webPageDescriptionFont->height); + auto descriptionh = qMin(_description.countHeight(textw), 3 * st::webPageDescriptionFont->height); + if (y >= skipy && y < skipy + descriptionh) { + result = _description.getStateLeft(x - skipx - st::msgPadding.left(), y - skipy, textw, _width, request.forText()); + if (!_title.isEmpty()) result.symbol += _title.length(); + return result; + } + skipy += descriptionh; } if (!_title.isEmpty() || !_description.isEmpty()) { skipy += st::webPagePhotoSkip; @@ -6246,16 +6304,29 @@ void HistoryLocation::getState(ClickHandlerPtr &lnk, HistoryCursorState &state, height -= skipy + st::mediaPadding.bottom(); } if (x >= skipx && y >= skipy && x < skipx + width && y < skipy + height && _data) { - lnk = _link; + result.link = _link; int32 fullRight = skipx + width, fullBottom = _height - (skipx ? st::mediaPadding.bottom() : 0); bool inDate = _parent->pointInTime(fullRight, fullBottom, x, y, InfoDisplayOverImage); if (inDate) { - state = HistoryInDateCursorState; + result.cursor = HistoryInDateCursorState; } - return; + return result; } + return result; +} + +TextSelection HistoryLocation::adjustSelection(TextSelection selection, TextSelectType type) const { + if (_description.isEmpty() || selection.to <= _title.length()) { + return _title.adjustSelection(selection, type); + } + auto descriptionSelection = _description.adjustSelection(toDescriptionSelection(selection), type); + if (selection.from >= _title.length()) { + return fromDescriptionSelection(descriptionSelection); + } + auto titleSelection = _title.adjustSelection(selection, type); + return { titleSelection.from, fromDescriptionSelection(descriptionSelection).to }; } const QString HistoryLocation::inDialogsText() const { @@ -6972,19 +7043,17 @@ void HistoryMessage::eraseFromOverview() { } } -QString HistoryMessage::selectedText(uint32 selection) const { +QString HistoryMessage::selectedText(TextSelection selection) const { QString result; if (_media && selection == FullSelection) { - QString text = _text.original(0, 0xFFFF, Text::ExpandLinksAll), mediaText = _media->inHistoryText(); + QString text = _text.original(AllTextSelection, Text::ExpandLinksAll), mediaText = _media->inHistoryText(); result = text.isEmpty() ? mediaText : (mediaText.isEmpty() ? text : (text + ' ' + mediaText)); } else { - uint16 selectedFrom = (selection == FullSelection) ? 0 : ((selection >> 16) & 0xFFFF); - uint16 selectedTo = (selection == FullSelection) ? 0xFFFF : (selection & 0xFFFF); - result = _text.original(selectedFrom, selectedTo, Text::ExpandLinksAll); + result = _text.original((selection == FullSelection) ? AllTextSelection : selection, Text::ExpandLinksAll); } if (auto fwd = Get()) { if (selection == FullSelection) { - QString fwdinfo = fwd->_text.original(0, 0xFFFF, Text::ExpandLinksAll), wrapped; + QString fwdinfo = fwd->_text.original(AllTextSelection, Text::ExpandLinksAll), wrapped; wrapped.reserve(fwdinfo.size() + 4 + result.size()); wrapped.append('[').append(fwdinfo).append(qsl("]\n")).append(result); result = wrapped; @@ -7002,7 +7071,7 @@ QString HistoryMessage::selectedText(uint32 selection) const { } QString HistoryMessage::inDialogsText() const { - return emptyText() ? (_media ? _media->inDialogsText() : QString()) : _text.original(0, 0xFFFF, Text::ExpandLinksNone); + return emptyText() ? (_media ? _media->inDialogsText() : QString()) : _text.original(AllTextSelection, Text::ExpandLinksNone); } HistoryMedia *HistoryMessage::getMedia() const { @@ -7224,7 +7293,7 @@ void HistoryMessage::setId(MsgId newId) { } } -void HistoryMessage::draw(Painter &p, const QRect &r, uint32 selection, uint64 ms) const { +void HistoryMessage::draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const { bool outbg = out() && !isPost(), bubble = drawBubble(), selected = (selection == FullSelection); int left = 0, width = 0, height = _height; @@ -7317,14 +7386,12 @@ void HistoryMessage::draw(Painter &p, const QRect &r, uint32 selection, uint64 m p.setPen(st::msgColor); p.setFont(st::msgFont); - uint16 selectedFrom = selected ? 0 : ((selection >> 16) & 0xFFFF); - uint16 selectedTo = selected ? 0 : (selection & 0xFFFF); - _text.draw(p, trect.x(), trect.y(), trect.width(), style::al_left, 0, -1, selectedFrom, selectedTo); + _text.draw(p, trect.x(), trect.y(), trect.width(), style::al_left, 0, -1, selection); if (_media && _media->isDisplayed()) { int32 top = height - marginBottom() - _media->height(); p.translate(left, top); - _media->draw(p, r.translated(-left, -top), selected, ms); + _media->draw(p, r.translated(-left, -top), toMediaSelection(selection), ms); p.translate(-left, -top); if (!_media->customInfoLayout()) { HistoryMessage::drawInfo(p, r.x() + r.width(), r.y() + r.height(), 2 * r.x() + r.width(), selected, InfoDisplayDefault); @@ -7335,7 +7402,7 @@ void HistoryMessage::draw(Painter &p, const QRect &r, uint32 selection, uint64 m } else { int32 top = marginTop(); p.translate(left, top); - _media->draw(p, r.translated(-left, -top), selected, ms); + _media->draw(p, r.translated(-left, -top), toMediaSelection(selection), ms); p.translate(-left, -top); } @@ -7537,22 +7604,18 @@ bool HistoryMessage::pointInTime(int32 right, int32 bottom, int x, int y, InfoDi return QRect(dateX, dateY, HistoryMessage::timeWidth(), st::msgDateFont->height).contains(x, y); } -void HistoryMessage::getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int x, int y) const { - lnk.clear(); - state = HistoryDefaultCursorState; +HistoryTextState HistoryMessage::getState(int x, int y, HistoryStateRequest request) const { + HistoryTextState result; int left = 0, width = 0, height = _height; countPositionAndSize(left, width); - if (width < 1) return; + if (width < 1) return result; - if (const ReplyKeyboard *keyboard = inlineReplyKeyboard()) { + auto keyboard = inlineReplyKeyboard(); + if (keyboard) { int h = st::msgBotKbButton.margin + keyboard->naturalHeight(); height -= h; - int top = height + st::msgBotKbButton.margin - marginBottom(); - if (x >= left && x < left + width && y >= top && y < _height - marginBottom()) { - return keyboard->getState(lnk, x - left, y - top); - } } if (drawBubble()) { @@ -7566,12 +7629,12 @@ void HistoryMessage::getState(ClickHandlerPtr &lnk, HistoryCursorState &state, i if (displayFromName()) { if (y >= trect.top() && y < trect.top() + st::msgNameFont->height) { if (x >= trect.left() && x < trect.left() + trect.width() && x < trect.left() + author()->nameText.maxWidth()) { - lnk = author()->openLink(); - return; + result.link = author()->openLink(); + return result; } if (via && !fwd && x >= trect.left() + author()->nameText.maxWidth() + st::msgServiceFont->spacew && x < trect.left() + author()->nameText.maxWidth() + st::msgServiceFont->spacew + via->_width) { - lnk = via->_lnk; - return; + result.link = via->_lnk; + return result; } } trect.setTop(trect.top() + st::msgNameFont->height); @@ -7579,22 +7642,29 @@ void HistoryMessage::getState(ClickHandlerPtr &lnk, HistoryCursorState &state, i if (displayForwardedFrom()) { int32 fwdheight = ((fwd->_text.maxWidth() > trect.width()) ? 2 : 1) * st::semiboldFont->height; if (y >= trect.top() && y < trect.top() + fwdheight) { - bool inText = false; bool breakEverywhere = (fwd->_text.countHeight(trect.width()) > 2 * st::semiboldFont->height); - textstyleSet(&st::inFwdTextStyle); - fwd->_text.getState(lnk, inText, x - trect.left(), y - trect.top(), trect.width(), style::al_left, breakEverywhere); - textstyleRestore(); + auto textRequest = request.forText(); if (breakEverywhere) { - state = HistoryInForwardedCursorState; + textRequest.flags |= Text::StateRequest::Flag::BreakEverywhere; } - return; + textstyleSet(&st::inFwdTextStyle); + result = fwd->_text.getState(x - trect.left(), y - trect.top(), trect.width(), textRequest); + textstyleRestore(); + result.symbol = 0; + result.afterSymbol = false; + if (breakEverywhere) { + result.cursor = HistoryInForwardedCursorState; + } else { + result.cursor = HistoryDefaultCursorState; + } + return result; } trect.setTop(trect.top() + fwdheight); } if (via && !displayFromName() && !displayForwardedFrom()) { if (x >= trect.left() && y >= trect.top() && y < trect.top() + st::msgNameFont->height && x < trect.left() + via->_width) { - lnk = via->_lnk; - return; + result.link = via->_lnk; + return result; } trect.setTop(trect.top() + st::msgNameFont->height); } @@ -7602,84 +7672,59 @@ void HistoryMessage::getState(ClickHandlerPtr &lnk, HistoryCursorState &state, i int32 h = st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom(); if (y >= trect.top() && y < trect.top() + h) { if (reply->replyToMsg && y >= trect.top() + st::msgReplyPadding.top() && y < trect.top() + st::msgReplyPadding.top() + st::msgReplyBarSize.height() && x >= trect.left() && x < trect.left() + trect.width()) { - lnk = reply->replyToLink(); + result.link = reply->replyToLink(); } - return; + return result; } trect.setTop(trect.top() + h); } - bool inDate = false; - - if (_media && _media->isDisplayed()) { - if (!_media->customInfoLayout()) { - inDate = HistoryMessage::pointInTime(r.x() + r.width(), r.y() + r.height(), x, y, InfoDisplayDefault); - } - if (y >= r.bottom() - _media->height() && y < r.bottom()) { - _media->getState(lnk, state, x - r.left(), y - (r.bottom() - _media->height())); - if (inDate) state = HistoryInDateCursorState; - return; - } - trect.setBottom(trect.bottom() - _media->height()); - } else { + bool inDate = false, mediaDisplayed = _media && _media->isDisplayed(); + if (!mediaDisplayed || !_media->customInfoLayout()) { inDate = HistoryMessage::pointInTime(r.x() + r.width(), r.y() + r.height(), x, y, InfoDisplayDefault); } - textstyleSet(&((out() && !isPost()) ? st::outTextStyle : st::inTextStyle)); - bool inText = false; - _text.getState(lnk, inText, x - trect.x(), y - trect.y(), trect.width()); - textstyleRestore(); - + if (mediaDisplayed) { + trect.setBottom(trect.bottom() - _media->height()); + if (y >= r.bottom() - _media->height()) { + result = _media->getState(x - r.left(), y - (r.bottom() - _media->height()), request); + result.symbol += _text.length(); + } + } + if (!mediaDisplayed || (y < r.bottom() - _media->height())) { + textstyleSet(&((out() && !isPost()) ? st::outTextStyle : st::inTextStyle)); + result = _text.getState(x - trect.x(), y - trect.y(), trect.width(), request.forText()); + textstyleRestore(); + } if (inDate) { - state = HistoryInDateCursorState; - } else if (inText) { - state = HistoryInTextCursorState; - } else { - state = HistoryDefaultCursorState; + result.cursor = HistoryInDateCursorState; } } else { - _media->getState(lnk, state, x - left, y - marginTop()); + result = _media->getState(x - left, y - marginTop(), request); + result.symbol += _text.length(); } + + if (keyboard) { + int top = height + st::msgBotKbButton.margin - marginBottom(); + if (x >= left && x < left + width && y >= top && y < _height - marginBottom()) { + result.link = keyboard->getState(x - left, y - top); + return result; + } + } + + return result; } -void HistoryMessage::getSymbol(uint16 &symbol, bool &after, bool &upon, int x, int y) const { - symbol = 0; - after = false; - upon = false; - - if (drawBubble()) { - int left = 0, width = 0, height = _height; - countPositionAndSize(left, width); - if (width < 1) return; - - auto fwd = Get(); - auto via = Get(); - auto reply = Get(); - - int top = marginTop(); - QRect r(left, top, width, height - top - marginBottom()); - QRect trect(r.marginsAdded(-st::msgPadding)); - if (displayFromName()) { - trect.setTop(trect.top() + st::msgNameFont->height); - } else if (via && !fwd) { - trect.setTop(trect.top() + st::msgNameFont->height); - } - if (displayForwardedFrom()) { - int32 fwdheight = ((fwd->_text.maxWidth() > trect.width()) ? 2 : 1) * st::semiboldFont->height; - trect.setTop(trect.top() + fwdheight); - } - if (reply) { - int32 h = st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom(); - trect.setTop(trect.top() + h); - } - if (_media && _media->isDisplayed()) { - trect.setBottom(trect.bottom() - _media->height()); - } - - textstyleSet(&((out() && !isPost()) ? st::outTextStyle : st::inTextStyle)); - _text.getSymbol(symbol, after, upon, x - trect.x(), y - trect.y(), trect.width()); - textstyleRestore(); +TextSelection HistoryMessage::adjustSelection(TextSelection selection, TextSelectType type) const { + if (!_media || selection.to <= _text.length()) { + return _text.adjustSelection(selection, type); } + auto mediaSelection = _media->adjustSelection(toMediaSelection(selection), type); + if (selection.from >= _text.length()) { + return fromMediaSelection(mediaSelection); + } + auto textSelection = _text.adjustSelection(selection, type); + return { textSelection.from, fromMediaSelection(mediaSelection).to }; } void HistoryMessage::drawInDialog(Painter &p, const QRect &r, bool act, const HistoryItem *&cacheFor, Text &cache) const { @@ -8019,14 +8064,12 @@ void HistoryService::countPositionAndSize(int32 &left, int32 &width) const { width = maxwidth - st::msgServiceMargin.left() - st::msgServiceMargin.left(); } -QString HistoryService::selectedText(uint32 selection) const { - uint16 selectedFrom = (selection == FullSelection) ? 0 : (selection >> 16) & 0xFFFF; - uint16 selectedTo = (selection == FullSelection) ? 0xFFFF : (selection & 0xFFFF); - return _text.original(selectedFrom, selectedTo); +QString HistoryService::selectedText(TextSelection selection) const { + return _text.original((selection == FullSelection) ? AllTextSelection : selection); } QString HistoryService::inDialogsText() const { - return _text.original(0, 0xFFFF, Text::ExpandLinksNone); + return _text.original(AllTextSelection, Text::ExpandLinksNone); } QString HistoryService::inReplyText() const { @@ -8041,7 +8084,7 @@ void HistoryService::setServiceText(const QString &text) { initDimensions(); } -void HistoryService::draw(Painter &p, const QRect &r, uint32 selection, uint64 ms) const { +void HistoryService::draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const { int left = 0, width = 0, height = _height - st::msgServiceMargin.top() - st::msgServiceMargin.bottom(); // two small margins countPositionAndSize(left, width); if (width < 1) return; @@ -8085,7 +8128,7 @@ void HistoryService::draw(Painter &p, const QRect &r, uint32 selection, uint64 m height -= st::msgServiceMargin.top() + _media->height(); int32 left = st::msgServiceMargin.left() + (width - _media->maxWidth()) / 2, top = st::msgServiceMargin.top() + height + st::msgServiceMargin.top(); p.translate(left, top); - _media->draw(p, r.translated(-left, -top), selection == FullSelection, ms); + _media->draw(p, r.translated(-left, -top), toMediaSelection(selection), ms); p.translate(-left, -top); } @@ -8102,9 +8145,7 @@ void HistoryService::draw(Painter &p, const QRect &r, uint32 selection, uint64 m p.setBrush(Qt::NoBrush); p.setPen(st::msgServiceColor); p.setFont(st::msgServiceFont); - uint16 selectedFrom = (selection == FullSelection) ? 0 : (selection >> 16) & 0xFFFF; - uint16 selectedTo = (selection == FullSelection) ? 0 : selection & 0xFFFF; - _text.draw(p, trect.x(), trect.y(), trect.width(), Qt::AlignCenter, 0, -1, selectedFrom, selectedTo); + _text.draw(p, trect.x(), trect.y(), trect.width(), Qt::AlignCenter, 0, -1, selection); textstyleRestore(); @@ -8166,13 +8207,12 @@ bool HistoryService::hasPoint(int x, int y) const { return QRect(left, st::msgServiceMargin.top(), width, height).contains(x, y); } -void HistoryService::getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int x, int y) const { - lnk.clear(); - state = HistoryDefaultCursorState; +HistoryTextState HistoryService::getState(int x, int y, HistoryStateRequest request) const { + HistoryTextState result; int left = 0, width = 0, height = _height - st::msgServiceMargin.top() - st::msgServiceMargin.bottom(); // two small margins countPositionAndSize(left, width); - if (width < 1) return; + if (width < 1) return result; if (int dateh = displayedDateHeight()) { y -= dateh; @@ -8190,41 +8230,14 @@ void HistoryService::getState(ClickHandlerPtr &lnk, HistoryCursorState &state, i QRect trect(QRect(left, st::msgServiceMargin.top(), width, height).marginsAdded(-st::msgServicePadding)); if (trect.contains(x, y)) { textstyleSet(&st::serviceTextStyle); - bool inText = false; - _text.getState(lnk, inText, x - trect.x(), y - trect.y(), trect.width(), Qt::AlignCenter); + auto textRequest = request.forText(); + textRequest.align = style::al_center; + result = _text.getState(x - trect.x(), y - trect.y(), trect.width(), textRequest); textstyleRestore(); - state = inText ? HistoryInTextCursorState : HistoryDefaultCursorState; } else if (_media) { - _media->getState(lnk, state, x - st::msgServiceMargin.left() - (width - _media->maxWidth()) / 2, y - st::msgServiceMargin.top() - height - st::msgServiceMargin.top()); + result = _media->getState(x - st::msgServiceMargin.left() - (width - _media->maxWidth()) / 2, y - st::msgServiceMargin.top() - height - st::msgServiceMargin.top(), request); } -} - -void HistoryService::getSymbol(uint16 &symbol, bool &after, bool &upon, int x, int y) const { - symbol = 0; - after = false; - upon = false; - - int left = 0, width = 0, height = _height - st::msgServiceMargin.top() - st::msgServiceMargin.bottom(); // two small margins - countPositionAndSize(left, width); - if (width < 1) return; - - if (int dateh = displayedDateHeight()) { - y -= dateh; - height -= dateh; - } - if (auto unreadbar = Get()) { - int unreadbarh = unreadbar->height(); - y -= unreadbarh; - height -= unreadbarh; - } - - if (_media) { - height -= st::msgServiceMargin.top() + _media->height(); - } - QRect trect(QRect(left, st::msgServiceMargin.top(), width, height).marginsAdded(-st::msgServicePadding)); - textstyleSet(&st::serviceTextStyle); - _text.getSymbol(symbol, after, upon, x - trect.x(), y - trect.y(), trect.width(), Qt::AlignCenter); - textstyleRestore(); + return result; } void HistoryService::drawInDialog(Painter &p, const QRect &r, bool act, const HistoryItem *&cacheFor, Text &cache) const { @@ -8272,13 +8285,12 @@ HistoryGroup::HistoryGroup(History *history, HistoryItem *newItem, const QDateTi , _lnk(new CommentsClickHandler(this)) { } -void HistoryGroup::getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int x, int y) const { - lnk.clear(); - state = HistoryDefaultCursorState; +HistoryTextState HistoryGroup::getState(int x, int y, HistoryStateRequest request) const { + HistoryTextState result; int32 left = 0, width = 0, height = _height - st::msgServiceMargin.top() - st::msgServiceMargin.bottom(); // two small margins countPositionAndSize(left, width); - if (width < 1) return; + if (width < 1) return result; QRect trect(QRect(left, st::msgServiceMargin.top(), width, height).marginsAdded(-st::msgServicePadding)); if (width > _maxw) { @@ -8286,8 +8298,9 @@ void HistoryGroup::getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int width = _maxw; } if (QRect(left, st::msgServiceMargin.top(), width, height).contains(x, y)) { - lnk = _lnk; + result.link = _lnk; } + return result; } void HistoryGroup::uniteWith(MsgId minId, MsgId maxId, int32 count) { @@ -8339,12 +8352,11 @@ HistoryCollapse::HistoryCollapse(History *history, MsgId wasMinId, const QDateTi , _wasMinId(wasMinId) { } -void HistoryCollapse::draw(Painter &p, const QRect &r, uint32 selection, uint64 ms) const { +void HistoryCollapse::draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const { } -void HistoryCollapse::getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int x, int y) const { - lnk.clear(); - state = HistoryDefaultCursorState; +HistoryTextState HistoryCollapse::getState(int x, int y, HistoryStateRequest request) const { + return HistoryTextState(); } HistoryJoined::HistoryJoined(History *history, const QDateTime &inviteDate, UserData *inviter, MTPDmessage::Flags flags) diff --git a/Telegram/SourceFiles/history.h b/Telegram/SourceFiles/history.h index ab525f3cb..d60c855f6 100644 --- a/Telegram/SourceFiles/history.h +++ b/Telegram/SourceFiles/history.h @@ -729,7 +729,7 @@ protected: }; -class HistoryMessage; // dynamic_cast optimize +class HistoryMessage; enum HistoryCursorState { HistoryDefaultCursorState, @@ -738,6 +738,36 @@ enum HistoryCursorState { HistoryInForwardedCursorState, }; +struct HistoryTextState { + HistoryTextState() = default; + HistoryTextState(const Text::StateResult &state) + : cursor(state.uponSymbol ? HistoryInTextCursorState : HistoryDefaultCursorState) + , link(state.link) + , afterSymbol(state.afterSymbol) + , symbol(state.symbol) { + } + HistoryTextState &operator=(const Text::StateResult &state) { + cursor = state.uponSymbol ? HistoryInTextCursorState : HistoryDefaultCursorState; + link = state.link; + afterSymbol = state.afterSymbol; + symbol = state.symbol; + return *this; + } + HistoryCursorState cursor = HistoryDefaultCursorState; + ClickHandlerPtr link; + bool afterSymbol = false; + uint16 symbol = 0; +}; + +struct HistoryStateRequest { + Text::StateRequest::Flags flags = Text::StateRequest::Flag::LookupLink; + Text::StateRequest forText() const { + Text::StateRequest result; + result.flags = flags; + return result; + } +}; + enum InfoDisplayType { InfoDisplayDefault, InfoDisplayOverImage, @@ -936,7 +966,7 @@ public: int naturalHeight() const; void paint(Painter &p, const QRect &clip) const; - void getState(ClickHandlerPtr &lnk, int x, int y) const; + ClickHandlerPtr getState(int x, int y) const; void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active); void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed); @@ -1068,7 +1098,7 @@ public: } return resizeGetHeight_(width); } - virtual void draw(Painter &p, const QRect &r, uint32 selection, uint64 ms) const = 0; + virtual void draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const = 0; virtual void dependencyItemRemoved(HistoryItem *dependency) { } @@ -1216,17 +1246,11 @@ public: virtual bool hasPoint(int x, int y) const { return false; } - virtual void getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int x, int y) const { - lnk.clear(); - state = HistoryDefaultCursorState; - } - virtual void getSymbol(uint16 &symbol, bool &after, bool &upon, int x, int y) const { // from text - upon = hasPoint(x, y); - symbol = upon ? 0xFFFF : 0; - after = false; - } - virtual uint32 adjustSelection(uint16 from, uint16 to, TextSelectType type) const { - return (from << 16) | to; + + virtual HistoryTextState getState(int x, int y, HistoryStateRequest request) const = 0; + + virtual TextSelection adjustSelection(TextSelection selection, TextSelectType type) const { + return selection; } // ClickHandlerHost interface @@ -1251,7 +1275,7 @@ public: } virtual void previousItemChanged(); - virtual QString selectedText(uint32 selection) const { + virtual QString selectedText(TextSelection selection) const { return qsl("[-]"); } virtual QString inDialogsText() const { @@ -1514,6 +1538,13 @@ protected: return const_cast(static_cast(this)->inlineReplyKeyboard()); } + TextSelection toMediaSelection(TextSelection selection) const { + return unshiftSelection(selection, _text); + } + TextSelection fromMediaSelection(TextSelection selection) const { + return shiftSelection(selection, _text); + } + Text _text = { int(st::msgMinWidth) }; int32 _textWidth, _textHeight; @@ -1624,8 +1655,8 @@ public: _width = qMin(width, _maxw); return _height; } - virtual void draw(Painter &p, const QRect &r, bool selected, uint64 ms) const = 0; - virtual void getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int x, int y) const = 0; + virtual void draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const = 0; + virtual HistoryTextState getState(int x, int y, HistoryStateRequest request) const = 0; // if we are in selecting items mode perhaps we want to // toggle selection instead of activating the pressed link @@ -1636,6 +1667,10 @@ public: return false; } + virtual TextSelection adjustSelection(TextSelection selection, TextSelectType type) const { + return selection; + } + // if we press and drag this link should we drag the item virtual bool dragItemByHandler(const ClickHandlerPtr &p) const = 0; @@ -1829,8 +1864,12 @@ public: void initDimensions() override; int resizeGetHeight(int width) override; - void draw(Painter &p, const QRect &r, bool selected, uint64 ms) const override; - void getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int x, int y) const override; + void draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const override; + HistoryTextState getState(int x, int y, HistoryStateRequest request) const override; + + TextSelection adjustSelection(TextSelection selection, TextSelectType type) const override { + return _caption.adjustSelection(selection, type); + } const QString inDialogsText() const override; const QString inHistoryText() const override; @@ -1902,8 +1941,12 @@ public: void initDimensions() override; int resizeGetHeight(int width) override; - void draw(Painter &p, const QRect &r, bool selected, uint64 ms) const override; - void getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int x, int y) const override; + void draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const override; + HistoryTextState getState(int x, int y, HistoryStateRequest request) const override; + + TextSelection adjustSelection(TextSelection selection, TextSelectType type) const override { + return _caption.adjustSelection(selection, type); + } const QString inDialogsText() const override; const QString inHistoryText() const override; @@ -2015,8 +2058,15 @@ public: void initDimensions() override; int resizeGetHeight(int width) override; - void draw(Painter &p, const QRect &r, bool selected, uint64 ms) const override; - void getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int x, int y) const override; + void draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const override; + HistoryTextState getState(int x, int y, HistoryStateRequest request) const override; + + TextSelection adjustSelection(TextSelection selection, TextSelectType type) const override { + if (auto captioned = Get()) { + return captioned->_caption.adjustSelection(selection, type); + } + return selection; + } const QString inDialogsText() const override; const QString inHistoryText() const override; @@ -2095,8 +2145,12 @@ public: void initDimensions() override; int resizeGetHeight(int width) override; - void draw(Painter &p, const QRect &r, bool selected, uint64 ms) const override; - void getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int x, int y) const override; + void draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const override; + HistoryTextState getState(int x, int y, HistoryStateRequest request) const override; + + TextSelection adjustSelection(TextSelection selection, TextSelectType type) const override { + return _caption.adjustSelection(selection, type); + } const QString inDialogsText() const override; const QString inHistoryText() const override; @@ -2183,8 +2237,8 @@ public: void initDimensions() override; int resizeGetHeight(int width) override; - void draw(Painter &p, const QRect &r, bool selected, uint64 ms) const override; - void getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int x, int y) const override; + void draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const override; + HistoryTextState getState(int x, int y, HistoryStateRequest request) const override; bool toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const override { return true; @@ -2255,8 +2309,8 @@ public: void initDimensions() override; - void draw(Painter &p, const QRect &r, bool selected, uint64 ms) const override; - void getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int x, int y) const override; + void draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const override; + HistoryTextState getState(int x, int y, HistoryStateRequest request) const override; bool toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const override { return true; @@ -2318,8 +2372,10 @@ public: void initDimensions() override; int resizeGetHeight(int width) override; - void draw(Painter &p, const QRect &r, bool selected, uint64 ms) const override; - void getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int x, int y) const override; + void draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const override; + HistoryTextState getState(int x, int y, HistoryStateRequest request) const override; + + TextSelection adjustSelection(TextSelection selection, TextSelectType type) const override; bool toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const override { return _attach && _attach->toggleSelectionByHandlerClick(p); @@ -2376,6 +2432,13 @@ public: ~HistoryWebPage(); private: + TextSelection toDescriptionSelection(TextSelection selection) const { + return unshiftSelection(selection, _title); + } + TextSelection fromDescriptionSelection(TextSelection selection) const { + return shiftSelection(selection, _title); + } + WebPageData *_data; ClickHandlerPtr _openl; HistoryMedia *_attach; @@ -2440,8 +2503,10 @@ public: void initDimensions() override; int resizeGetHeight(int32 width) override; - void draw(Painter &p, const QRect &r, bool selected, uint64 ms) const override; - void getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int x, int y) const override; + void draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const override; + HistoryTextState getState(int x, int y, HistoryStateRequest request) const override; + + TextSelection adjustSelection(TextSelection selection, TextSelectType type) const override; bool toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const override { return p == _link; @@ -2467,6 +2532,13 @@ public: } private: + TextSelection toDescriptionSelection(TextSelection selection) const { + return unshiftSelection(selection, _title); + } + TextSelection fromDescriptionSelection(TextSelection selection) const { + return shiftSelection(selection, _title); + } + LocationData *_data; Text _title, _description; ClickHandlerPtr _link; @@ -2538,7 +2610,7 @@ public: void drawInfo(Painter &p, int32 right, int32 bottom, int32 width, bool selected, InfoDisplayType type) const override; void setViewsCount(int32 count) override; void setId(MsgId newId) override; - void draw(Painter &p, const QRect &r, uint32 selection, uint64 ms) const override; + void draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const override; void dependencyItemRemoved(HistoryItem *dependency) override; @@ -2547,12 +2619,9 @@ public: bool hasPoint(int x, int y) const override; bool pointInTime(int32 right, int32 bottom, int x, int y, InfoDisplayType type) const override; - void getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int x, int y) const override; + HistoryTextState getState(int x, int y, HistoryStateRequest request) const override; - void getSymbol(uint16 &symbol, bool &after, bool &upon, int x, int y) const override; - uint32 adjustSelection(uint16 from, uint16 to, TextSelectType type) const override { - return _text.adjustSelection(from, to, type); - } + TextSelection adjustSelection(TextSelection selection, TextSelectType type) const override; // ClickHandlerHost interface void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override { @@ -2573,7 +2642,7 @@ public: int32 addToOverview(AddToOverviewMethod method) override; void eraseFromOverview(); - QString selectedText(uint32 selection) const override; + QString selectedText(TextSelection selection) const override; QString inDialogsText() const override; HistoryMedia *getMedia() const override; void setText(const QString &text, const EntitiesInText &entities) override; @@ -2763,12 +2832,12 @@ public: void countPositionAndSize(int32 &left, int32 &width) const; - void draw(Painter &p, const QRect &r, uint32 selection, uint64 ms) const override; + void draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const override; bool hasPoint(int x, int y) const override; - void getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int x, int y) const override; - void getSymbol(uint16 &symbol, bool &after, bool &upon, int x, int y) const override; - uint32 adjustSelection(uint16 from, uint16 to, TextSelectType type) const override { - return _text.adjustSelection(from, to, type); + HistoryTextState getState(int x, int y, HistoryStateRequest request) const override; + + TextSelection adjustSelection(TextSelection selection, TextSelectType type) const override { + return _text.adjustSelection(selection, type); } void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override { @@ -2789,7 +2858,7 @@ public: bool serviceMsg() const override { return true; } - QString selectedText(uint32 selection) const override; + QString selectedText(TextSelection selection) const override; QString inDialogsText() const override; QString inReplyText() const override; @@ -2824,12 +2893,8 @@ public: return _create(history, newItem, date); } - void getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int x, int y) const; - void getSymbol(uint16 &symbol, bool &after, bool &upon, int x, int y) const { - symbol = 0xFFFF; - after = false; - upon = false; - } + HistoryTextState getState(int x, int y, HistoryStateRequest request) const; + QString selectedText(uint32 selection) const { return QString(); } @@ -2877,13 +2942,9 @@ public: return _create(history, wasMinId, date); } - void draw(Painter &p, const QRect &r, uint32 selection, uint64 ms) const; - void getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int x, int y) const; - void getSymbol(uint16 &symbol, bool &after, bool &upon, int x, int y) const { - symbol = 0xFFFF; - after = false; - upon = false; - } + void draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const; + HistoryTextState getState(int x, int y, HistoryStateRequest request) const; + QString selectedText(uint32 selection) const { return QString(); } diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp index 73efbe930..2c3aa309d 100644 --- a/Telegram/SourceFiles/historywidget.cpp +++ b/Telegram/SourceFiles/historywidget.cpp @@ -252,11 +252,13 @@ void HistoryInner::paintEvent(QPaintEvent *e) { p.save(); p.translate(0, y); if (r.y() < y + item->height()) while (y < drawToY) { - uint32 sel = 0; + TextSelection sel; if (y >= selfromy && y < seltoy) { - sel = (_dragSelecting && !item->serviceMsg() && item->id > 0) ? FullSelection : 0; + if (_dragSelecting && !item->serviceMsg() && item->id > 0) { + sel = FullSelection; + } } else if (hasSel) { - SelectedItems::const_iterator i = _selected.constFind(item); + auto i = _selected.constFind(item); if (i != selEnd) { sel = i.value(); } @@ -297,11 +299,13 @@ void HistoryInner::paintEvent(QPaintEvent *e) { while (y < drawToY) { int32 h = item->height(); if (historyRect.y() < y + h && hdrawtop < y + h) { - uint32 sel = 0; + TextSelection sel; if (y >= selfromy && y < seltoy) { - sel = (_dragSelecting && !item->serviceMsg() && item->id > 0) ? FullSelection : 0; + if (_dragSelecting && !item->serviceMsg() && item->id > 0) { + sel = FullSelection; + } } else if (hasSel) { - SelectedItems::const_iterator i = _selected.constFind(item); + auto i = _selected.constFind(item); if (i != selEnd) { sel = i.value(); } @@ -588,19 +592,20 @@ void HistoryInner::dragActionStart(const QPoint &screenPos, Qt::MouseButton butt } } if (_dragAction == NoDrag && _dragItem) { - bool afterDragSymbol, uponSymbol; - uint16 symbol; + HistoryTextState dragState; if (_trippleClickTimer.isActive() && (screenPos - _trippleClickPoint).manhattanLength() < QApplication::startDragDistance()) { - _dragItem->getSymbol(symbol, afterDragSymbol, uponSymbol, _dragStartPos.x(), _dragStartPos.y()); - if (uponSymbol) { - uint32 selStatus = (symbol << 16) | symbol; + HistoryStateRequest request; + request.flags = Text::StateRequest::Flag::LookupSymbol; + dragState = _dragItem->getState(_dragStartPos.x(), _dragStartPos.y(), request); + if (dragState.cursor == HistoryInTextCursorState) { + TextSelection selStatus = { dragState.symbol, dragState.symbol }; if (selStatus != FullSelection && (_selected.isEmpty() || _selected.cbegin().value() != FullSelection)) { if (!_selected.isEmpty()) { repaintItem(_selected.cbegin().key()); _selected.clear(); } _selected.insert(_dragItem, selStatus); - _dragSymbol = symbol; + _dragSymbol = dragState.symbol; _dragAction = Selecting; _dragSelType = TextSelectParagraphs; dragActionUpdate(_dragPos); @@ -608,12 +613,14 @@ void HistoryInner::dragActionStart(const QPoint &screenPos, Qt::MouseButton butt } } } else if (App::pressedItem()) { - _dragItem->getSymbol(symbol, afterDragSymbol, uponSymbol, _dragStartPos.x(), _dragStartPos.y()); + HistoryStateRequest request; + request.flags = Text::StateRequest::Flag::LookupSymbol; + dragState = _dragItem->getState(_dragStartPos.x(), _dragStartPos.y(), request); } if (_dragSelType != TextSelectParagraphs) { if (App::pressedItem()) { - _dragSymbol = symbol; - bool uponSelected = uponSymbol; + _dragSymbol = dragState.symbol; + bool uponSelected = (dragState.cursor == HistoryInTextCursorState); if (uponSelected) { if (_selected.isEmpty() || _selected.cbegin().value() == FullSelection || @@ -621,7 +628,7 @@ void HistoryInner::dragActionStart(const QPoint &screenPos, Qt::MouseButton butt ) { uponSelected = false; } else { - uint16 selFrom = (_selected.cbegin().value() >> 16) & 0xFFFF, selTo = _selected.cbegin().value() & 0xFFFF; + uint16 selFrom = _selected.cbegin().value().from, selTo = _selected.cbegin().value().to; if (_dragSymbol < selFrom || _dragSymbol >= selTo) { uponSelected = false; } @@ -633,8 +640,8 @@ void HistoryInner::dragActionStart(const QPoint &screenPos, Qt::MouseButton butt if (dynamic_cast(App::pressedItem()->getMedia()) || _dragCursorState == HistoryInDateCursorState) { _dragAction = PrepareDrag; // start sticker drag or by-date drag } else { - if (afterDragSymbol) ++_dragSymbol; - uint32 selStatus = (_dragSymbol << 16) | _dragSymbol; + if (dragState.afterSymbol) ++_dragSymbol; + TextSelection selStatus = { _dragSymbol, _dragSymbol }; if (selStatus != FullSelection && (_selected.isEmpty() || _selected.cbegin().value() != FullSelection)) { if (!_selected.isEmpty()) { repaintItem(_selected.cbegin().key()); @@ -675,12 +682,13 @@ void HistoryInner::onDragExec() { bool uponSelected = false; if (_dragItem) { - bool afterDragSymbol; - uint16 symbol; if (!_selected.isEmpty() && _selected.cbegin().value() == FullSelection) { uponSelected = _selected.contains(_dragItem); } else { - _dragItem->getSymbol(symbol, afterDragSymbol, uponSelected, _dragStartPos.x(), _dragStartPos.y()); + HistoryStateRequest request; + request.flags |= Text::StateRequest::Flag::LookupSymbol; + auto dragState = _dragItem->getState(_dragStartPos.x(), _dragStartPos.y(), request); + uponSelected = (dragState.cursor == HistoryInTextCursorState); if (uponSelected) { if (_selected.isEmpty() || _selected.cbegin().value() == FullSelection || @@ -688,8 +696,8 @@ void HistoryInner::onDragExec() { ) { uponSelected = false; } else { - uint16 selFrom = (_selected.cbegin().value() >> 16) & 0xFFFF, selTo = _selected.cbegin().value() & 0xFFFF; - if (symbol < selFrom || symbol >= selTo) { + uint16 selFrom = _selected.cbegin().value().from, selTo = _selected.cbegin().value().to; + if (dragState.symbol < selFrom || dragState.symbol >= selTo) { uponSelected = false; } } @@ -841,8 +849,8 @@ void HistoryInner::dragActionFinish(const QPoint &screenPos, Qt::MouseButton but applyDragSelection(); _dragSelFrom = _dragSelTo = 0; } else if (!_selected.isEmpty() && !_dragWasInactive) { - uint32 sel = _selected.cbegin().value(); - if (sel != FullSelection && (sel & 0xFFFF) == ((sel >> 16) & 0xFFFF)) { + auto sel = _selected.cbegin().value(); + if (sel != FullSelection && sel.from == sel.to) { _selected.clear(); if (App::wnd()) App::wnd()->setInnerFocus(); } @@ -867,15 +875,15 @@ void HistoryInner::mouseDoubleClickEvent(QMouseEvent *e) { dragActionStart(e->globalPos(), e->button()); if (((_dragAction == Selecting && !_selected.isEmpty() && _selected.cbegin().value() != FullSelection) || (_dragAction == NoDrag && (_selected.isEmpty() || _selected.cbegin().value() != FullSelection))) && _dragSelType == TextSelectLetters && _dragItem) { - bool afterDragSymbol, uponSelected; - uint16 symbol; - _dragItem->getSymbol(symbol, afterDragSymbol, uponSelected, _dragStartPos.x(), _dragStartPos.y()); - if (uponSelected) { - _dragSymbol = symbol; + HistoryStateRequest request; + request.flags |= Text::StateRequest::Flag::LookupSymbol; + auto dragState = _dragItem->getState(_dragStartPos.x(), _dragStartPos.y(), request); + if (dragState.cursor == HistoryInTextCursorState) { + _dragSymbol = dragState.symbol; _dragSelType = TextSelectWords; if (_dragAction == NoDrag) { _dragAction = Selecting; - uint32 selStatus = (symbol << 16) | symbol; + TextSelection selStatus = { dragState.symbol, dragState.symbol }; if (!_selected.isEmpty()) { repaintItem(_selected.cbegin().key()); _selected.clear(); @@ -915,13 +923,14 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { isUponSelected = -2; } } else { - uint16 symbol, selFrom = (_selected.cbegin().value() >> 16) & 0xFFFF, selTo = _selected.cbegin().value() & 0xFFFF; + uint16 selFrom = _selected.cbegin().value().from, selTo = _selected.cbegin().value().to; hasSelected = (selTo > selFrom) ? 1 : 0; if (App::mousedItem() && App::mousedItem() == App::hoveredItem()) { QPoint mousePos(mapMouseToItem(mapFromGlobal(_dragPos), App::mousedItem())); - bool afterDragSymbol, uponSymbol; - App::mousedItem()->getSymbol(symbol, afterDragSymbol, uponSymbol, mousePos.x(), mousePos.y()); - if (uponSymbol && symbol >= selFrom && symbol < selTo) { + HistoryStateRequest request; + request.flags |= Text::StateRequest::Flag::LookupSymbol; + auto dragState = App::mousedItem()->getState(mousePos.x(), mousePos.y(), request); + if (dragState.cursor == HistoryInTextCursorState && dragState.symbol >= selFrom && dragState.symbol < selTo) { isUponSelected = 1; } } @@ -1685,57 +1694,13 @@ void HistoryInner::onUpdateSelected() { dragActionCancel(); } - ClickHandlerPtr lnk; + HistoryTextState dragState; ClickHandlerHost *lnkhost = nullptr; - HistoryCursorState cursorState = HistoryDefaultCursorState; + bool selectingText = (item == _dragItem && item == App::hoveredItem() && !_selected.isEmpty() && _selected.cbegin().value() != FullSelection); if (point.y() < _historyOffset) { if (_botAbout && !_botAbout->info->text.isEmpty() && _botAbout->height > 0) { - bool inText = false; - _botAbout->info->text.getState(lnk, inText, point.x() - _botAbout->rect.left() - st::msgPadding.left(), point.y() - _botAbout->rect.top() - st::msgPadding.top() - st::botDescSkip - st::msgNameFont->height, _botAbout->width); + dragState = _botAbout->info->text.getState(point.x() - _botAbout->rect.left() - st::msgPadding.left(), point.y() - _botAbout->rect.top() - st::msgPadding.top() - st::botDescSkip - st::msgNameFont->height, _botAbout->width); lnkhost = _botAbout.get(); - cursorState = inText ? HistoryInTextCursorState : HistoryDefaultCursorState; - } - } else if (item) { - item->getState(lnk, cursorState, m.x(), m.y()); - lnkhost = item; - if (!lnk && m.x() >= st::msgMargin.left() && m.x() < st::msgMargin.left() + st::msgPhotoSize) { - if (HistoryMessage *msg = item->toHistoryMessage()) { - if (msg->hasFromPhoto()) { - enumerateUserpics([&lnk, &lnkhost, &point](HistoryMessage *message, int userpicTop) -> bool { - // stop enumeration if the userpic is above our point - if (userpicTop + st::msgPhotoSize <= point.y()) { - return false; - } - - // stop enumeration if we've found a userpic under the cursor - if (point.y() >= userpicTop && point.y() < userpicTop + st::msgPhotoSize) { - lnk = message->from()->openLink(); - lnkhost = message; - return false; - } - return true; - }); - } - } - } - } - bool lnkChanged = ClickHandler::setActive(lnk, lnkhost); - if (lnkChanged || cursorState != _dragCursorState) { - PopupTooltip::Hide(); - } - if (lnk || cursorState == HistoryInDateCursorState || cursorState == HistoryInForwardedCursorState) { - PopupTooltip::Show(1000, this); - } - - Qt::CursorShape cur = style::cur_default; - if (_dragAction == NoDrag) { - _dragCursorState = cursorState; - if (lnk) { - cur = style::cur_pointer; - } else if (_dragCursorState == HistoryInTextCursorState && (_selected.isEmpty() || _selected.cbegin().value() != FullSelection)) { - cur = style::cur_text; - } else if (_dragCursorState == HistoryInDateCursorState) { -// cur = style::cur_cross; } } else if (item) { if (item != _dragItem || (m - _dragStartPos).manhattanLength() >= QApplication::startDragDistance()) { @@ -1746,19 +1711,68 @@ void HistoryInner::onUpdateSelected() { _dragAction = Selecting; } } + + HistoryStateRequest request; if (_dragAction == Selecting) { - bool canSelectMany = (_history != 0); - if (item == _dragItem && item == App::hoveredItem() && !_selected.isEmpty() && _selected.cbegin().value() != FullSelection) { - bool afterSymbol, uponSymbol; - uint16 second; - _dragItem->getSymbol(second, afterSymbol, uponSymbol, m.x(), m.y()); - if (afterSymbol && _dragSelType == TextSelectLetters) ++second; - uint32 selState = _dragItem->adjustSelection(qMin(second, _dragSymbol), qMax(second, _dragSymbol), _dragSelType); + request.flags |= Text::StateRequest::Flag::LookupSymbol; + } else { + selectingText = false; + } + dragState = item->getState(m.x(), m.y(), request); + lnkhost = item; + if (!dragState.link && m.x() >= st::msgMargin.left() && m.x() < st::msgMargin.left() + st::msgPhotoSize) { + if (HistoryMessage *msg = item->toHistoryMessage()) { + if (msg->hasFromPhoto()) { + enumerateUserpics([&dragState, &lnkhost, &point](HistoryMessage *message, int userpicTop) -> bool { + // stop enumeration if the userpic is above our point + if (userpicTop + st::msgPhotoSize <= point.y()) { + return false; + } + + // stop enumeration if we've found a userpic under the cursor + if (point.y() >= userpicTop && point.y() < userpicTop + st::msgPhotoSize) { + dragState.link = message->from()->openLink(); + lnkhost = message; + return false; + } + return true; + }); + } + } + } + } + bool lnkChanged = ClickHandler::setActive(dragState.link, lnkhost); + if (lnkChanged || dragState.cursor != _dragCursorState) { + PopupTooltip::Hide(); + } + if (dragState.link || dragState.cursor == HistoryInDateCursorState || dragState.cursor == HistoryInForwardedCursorState) { + PopupTooltip::Show(1000, this); + } + + Qt::CursorShape cur = style::cur_default; + if (_dragAction == NoDrag) { + _dragCursorState = dragState.cursor; + if (dragState.link) { + cur = style::cur_pointer; + } else if (_dragCursorState == HistoryInTextCursorState && (_selected.isEmpty() || _selected.cbegin().value() != FullSelection)) { + cur = style::cur_text; + } else if (_dragCursorState == HistoryInDateCursorState) { +// cur = style::cur_cross; + } + } else if (item) { + if (_dragAction == Selecting) { + bool canSelectMany = (_history != nullptr); + if (selectingText) { + uint16 second = dragState.symbol; + if (dragState.afterSymbol && _dragSelType == TextSelectLetters) { + ++second; + } + auto selState = _dragItem->adjustSelection({ qMin(second, _dragSymbol), qMax(second, _dragSymbol) }, _dragSelType); if (_selected[_dragItem] != selState) { _selected[_dragItem] = selState; repaintItem(_dragItem); } - if (!_wasSelectedText && (selState == FullSelection || (selState & 0xFFFF) != ((selState >> 16) & 0xFFFF))) { + if (!_wasSelectedText && (selState == FullSelection || selState.from != selState.to)) { _wasSelectedText = true; setFocus(); } @@ -2009,7 +2023,7 @@ QString HistoryInner::tooltipText() const { } else if (_dragCursorState == HistoryInForwardedCursorState && _dragAction == NoDrag) { if (App::hoveredItem()) { if (HistoryMessageForwarded *fwd = App::hoveredItem()->Get()) { - return fwd->_text.original(0, 0xFFFF, Text::ExpandLinksNone); + return fwd->_text.original(AllTextSelection, Text::ExpandLinksNone); } } } else if (ClickHandlerPtr lnk = ClickHandler::getActive()) { @@ -2367,11 +2381,10 @@ void BotKeyboard::updateSelected() { QPoint p(mapFromGlobal(_lastMousePos)); int x = rtl() ? st::botKbScroll.width : _st->margin; - ClickHandlerPtr lnk; - _impl->getState(lnk, p.x() - x, p.y() - _st->margin); - if (ClickHandler::setActive(lnk, this)) { + auto link = _impl->getState(p.x() - x, p.y() - _st->margin); + if (ClickHandler::setActive(link, this)) { PopupTooltip::Hide(); - setCursor(lnk ? style::cur_pointer : style::cur_default); + setCursor(link ? style::cur_pointer : style::cur_default); } } @@ -5927,6 +5940,7 @@ void HistoryWidget::inlineBotChanged() { _inlineBotCancel = std_::make_unique(this, st::inlineBotCancel); connect(_inlineBotCancel.get(), SIGNAL(clicked()), this, SLOT(onInlineBotCancel())); _inlineBotCancel->setGeometry(_send.geometry()); + _attachEmoji.raise(); updateFieldSubmitSettings(); updateControlsVisibility(); } else if (!isInlineBot && _inlineBotCancel) { diff --git a/Telegram/SourceFiles/historywidget.h b/Telegram/SourceFiles/historywidget.h index 99d07a0d9..9e9b77a12 100644 --- a/Telegram/SourceFiles/historywidget.h +++ b/Telegram/SourceFiles/historywidget.h @@ -174,7 +174,7 @@ private: bool _firstLoading = false; style::cursor _cursor = style::cur_default; - typedef QMap SelectedItems; + using SelectedItems = QMap; SelectedItems _selected; void applyDragSelection(); void applyDragSelection(SelectedItems *toItems) const; diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp index 1443e383b..a18ec139a 100644 --- a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp +++ b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp @@ -125,7 +125,7 @@ void DeleteSavedGifClickHandler::onClickImpl() const { if (App::main()) emit App::main()->savedGifsUpdated(); } -void Gif::paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const { +void Gif::paint(Painter &p, const QRect &clip, const PaintContext *context) const { DocumentData *document = getShownDocument(); document->automaticLoad(nullptr); @@ -381,7 +381,7 @@ void Sticker::preload() const { } } -void Sticker::paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const { +void Sticker::paint(Painter &p, const QRect &clip, const PaintContext *context) const { bool loaded = getShownDocument()->loaded(); float64 over = _a_over.isNull() ? (_active ? 1 : 0) : _a_over.current(); @@ -473,7 +473,7 @@ void Photo::initDimensions() { _minh = st::inlineMediaHeight + st::inlineResultsSkip; } -void Photo::paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const { +void Photo::paint(Painter &p, const QRect &clip, const PaintContext *context) const { int32 height = st::inlineMediaHeight; QSize frame = countFrameSize(); @@ -591,7 +591,7 @@ void Video::initDimensions() { _minh += st::inlineRowMargin * 2 + st::inlineRowBorder; } -void Video::paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const { +void Video::paint(Painter &p, const QRect &clip, const PaintContext *context) const { int left = st::inlineThumbSize + st::inlineThumbSkip; bool withThumb = !content_thumb()->isNull(); @@ -694,7 +694,7 @@ void File::initDimensions() { _minh += st::inlineRowMargin * 2 + st::inlineRowBorder; } -void File::paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const { +void File::paint(Painter &p, const QRect &clip, const PaintContext *context) const { int32 left = st::msgFileSize + st::inlineThumbSkip; DocumentData *document = getShownDocument(); @@ -942,7 +942,7 @@ int32 Contact::resizeGetHeight(int32 width) { return _height; } -void Contact::paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const { +void Contact::paint(Painter &p, const QRect &clip, const PaintContext *context) const { int32 left = st::emojiPanHeaderLeft - st::inlineResultsLeft; left = st::msgFileSize + st::inlineThumbSkip; @@ -1051,7 +1051,7 @@ int32 Article::resizeGetHeight(int32 width) { return _height; } -void Article::paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const { +void Article::paint(Painter &p, const QRect &clip, const PaintContext *context) const { int32 left = st::emojiPanHeaderLeft - st::inlineResultsLeft; if (_withThumb) { left = st::inlineThumbSize + st::inlineThumbSkip; diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.h b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.h index b6d769b75..e61c975e3 100644 --- a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.h +++ b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.h @@ -70,7 +70,7 @@ public: return true; } - void paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const override; + void paint(Painter &p, const QRect &clip, const PaintContext *context) const override; void getState(ClickHandlerPtr &link, HistoryCursorState &cursor, int x, int y) const override; // ClickHandlerHost interface @@ -135,7 +135,7 @@ public: return true; } - void paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const override; + void paint(Painter &p, const QRect &clip, const PaintContext *context) const override; void getState(ClickHandlerPtr &link, HistoryCursorState &cursor, int x, int y) const override; private: @@ -165,7 +165,7 @@ public: } void preload() const override; - void paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const override; + void paint(Painter &p, const QRect &clip, const PaintContext *context) const override; void getState(ClickHandlerPtr &link, HistoryCursorState &cursor, int x, int y) const override; // ClickHandlerHost interface @@ -190,7 +190,7 @@ public: void initDimensions() override; - void paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const override; + void paint(Painter &p, const QRect &clip, const PaintContext *context) const override; void getState(ClickHandlerPtr &link, HistoryCursorState &cursor, int x, int y) const override; private: @@ -238,7 +238,7 @@ public: void initDimensions() override; - void paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const override; + void paint(Painter &p, const QRect &clip, const PaintContext *context) const override; void getState(ClickHandlerPtr &link, HistoryCursorState &cursor, int x, int y) const override; // ClickHandlerHost interface @@ -302,7 +302,7 @@ public: void initDimensions() override; int resizeGetHeight(int width) override; - void paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const override; + void paint(Painter &p, const QRect &clip, const PaintContext *context) const override; void getState(ClickHandlerPtr &link, HistoryCursorState &cursor, int x, int y) const override; private: @@ -321,7 +321,7 @@ public: void initDimensions() override; int resizeGetHeight(int width) override; - void paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const override; + void paint(Painter &p, const QRect &clip, const PaintContext *context) const override; void getState(ClickHandlerPtr &link, HistoryCursorState &cursor, int x, int y) const override; private: diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_layout_item.h b/Telegram/SourceFiles/inline_bots/inline_bot_layout_item.h index 71284328f..0c091845b 100644 --- a/Telegram/SourceFiles/inline_bots/inline_bot_layout_item.h +++ b/Telegram/SourceFiles/inline_bots/inline_bot_layout_item.h @@ -58,7 +58,7 @@ public: //ItemBase(PhotoData *photo) : _photo(photo) { //} - virtual void paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const = 0; + virtual void paint(Painter &p, const QRect &clip, const PaintContext *context) const = 0; virtual void setPosition(int32 position); int32 position() const; diff --git a/Telegram/SourceFiles/layout.h b/Telegram/SourceFiles/layout.h index 7dee8bc15..86e6298e2 100644 --- a/Telegram/SourceFiles/layout.h +++ b/Telegram/SourceFiles/layout.h @@ -20,7 +20,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once -static const uint32 FullSelection = 0xFFFFFFFF; +static constexpr TextSelection FullSelection = { 0xFFFF, 0xFFFF }; extern TextParseOptions _textNameOptions, _textDlgOptions; extern TextParseOptions _historyTextOptions, _historyBotOptions, _historyTextNoMonoOptions, _historyBotNoMonoOptions; diff --git a/Telegram/SourceFiles/mediaview.cpp b/Telegram/SourceFiles/mediaview.cpp index 16f4b018a..24d0471ac 100644 --- a/Telegram/SourceFiles/mediaview.cpp +++ b/Telegram/SourceFiles/mediaview.cpp @@ -1781,13 +1781,14 @@ bool MediaView::updateOverState(OverState newState) { void MediaView::updateOver(QPoint pos) { ClickHandlerPtr lnk; ClickHandlerHost *lnkhost = nullptr; - bool inText; if (_saveMsgStarted && _saveMsg.contains(pos)) { - _saveMsgText.getState(lnk, inText, pos.x() - _saveMsg.x() - st::medviewSaveMsgPadding.left(), pos.y() - _saveMsg.y() - st::medviewSaveMsgPadding.top(), _saveMsg.width() - st::medviewSaveMsgPadding.left() - st::medviewSaveMsgPadding.right()); + auto textState = _saveMsgText.getState(pos.x() - _saveMsg.x() - st::medviewSaveMsgPadding.left(), pos.y() - _saveMsg.y() - st::medviewSaveMsgPadding.top(), _saveMsg.width() - st::medviewSaveMsgPadding.left() - st::medviewSaveMsgPadding.right()); + lnk = textState.link; lnkhost = this; } else if (_captionRect.contains(pos)) { - _caption.getState(lnk, inText, pos.x() - _captionRect.x(), pos.y() - _captionRect.y(), _captionRect.width()); + auto textState = _caption.getState(pos.x() - _captionRect.x(), pos.y() - _captionRect.y(), _captionRect.width()); + lnk = textState.link; lnkhost = this; } diff --git a/Telegram/SourceFiles/overview/overview_layout.cpp b/Telegram/SourceFiles/overview/overview_layout.cpp index 3a789f3c6..b830e0205 100644 --- a/Telegram/SourceFiles/overview/overview_layout.cpp +++ b/Telegram/SourceFiles/overview/overview_layout.cpp @@ -131,7 +131,7 @@ void Date::initDimensions() { _minh = st::linksDateMargin.top() + st::normalFont->height + st::linksDateMargin.bottom() + st::linksBorder; } -void Date::paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const { +void Date::paint(Painter &p, const QRect &clip, TextSelection selection, const PaintContext *context) const { if (clip.intersects(QRect(0, st::linksDateMargin.top(), _width, st::normalFont->height))) { p.setPen(st::linksDateColor); p.setFont(st::semiboldFont); @@ -159,7 +159,7 @@ int32 Photo::resizeGetHeight(int32 width) { return _height; } -void Photo::paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const { +void Photo::paint(Painter &p, const QRect &clip, TextSelection selection, const PaintContext *context) const { bool good = _data->loaded(); if (!good) { _data->medium->automaticLoad(_parent); @@ -230,7 +230,7 @@ int32 Video::resizeGetHeight(int32 width) { return _height; } -void Video::paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const { +void Video::paint(Painter &p, const QRect &clip, TextSelection selection, const PaintContext *context) const { bool selected = (selection == FullSelection), thumbLoaded = _data->thumb->loaded(); _data->automaticLoad(_parent); @@ -397,7 +397,7 @@ void Voice::initDimensions() { _minh = st::msgFilePadding.top() + st::msgFileSize + st::msgFilePadding.bottom() + st::lineWidth; } -void Voice::paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const { +void Voice::paint(Painter &p, const QRect &clip, TextSelection selection, const PaintContext *context) const { bool selected = (selection == FullSelection); _data->automaticLoad(_parent); @@ -515,9 +515,9 @@ void Voice::getState(ClickHandlerPtr &link, HistoryCursorState &cursor, int x, i } if (rtlrect(nameleft, statustop, _width - nameleft - nameright, st::normalFont->height, _width).contains(x, y)) { if (_statusSize == FileStatusSizeLoaded || _statusSize == FileStatusSizeReady) { - bool inText = false; - _details.getStateLeft(link, inText, x - nameleft, y - statustop, _width, _width); - cursor = inText ? HistoryInTextCursorState : HistoryDefaultCursorState; + auto textState = _details.getStateLeft(x - nameleft, y - statustop, _width, _width); + link = textState.link; + cursor = textState.uponSymbol ? HistoryInTextCursorState : HistoryDefaultCursorState; } } if (hasPoint(x, y) && !link && !_data->loading()) { @@ -615,7 +615,7 @@ void Document::initDimensions() { } } -void Document::paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const { +void Document::paint(Painter &p, const QRect &clip, TextSelection selection, const PaintContext *context) const { bool selected = (selection == FullSelection); _data->automaticLoad(_parent); @@ -1027,7 +1027,7 @@ int32 Link::resizeGetHeight(int32 width) { return _height; } -void Link::paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const { +void Link::paint(Painter &p, const QRect &clip, TextSelection selection, const PaintContext *context) const { int32 left = st::dlgPhotoSize + st::dlgPhotoPadding, top = st::linksMargin.top() + st::linksBorder, w = _width - left; if (clip.intersects(rtlrect(0, top, st::dlgPhotoSize, st::dlgPhotoSize, _width))) { if (_page && _page->photo) { diff --git a/Telegram/SourceFiles/overview/overview_layout.h b/Telegram/SourceFiles/overview/overview_layout.h index 9a155d01c..ed46ee808 100644 --- a/Telegram/SourceFiles/overview/overview_layout.h +++ b/Telegram/SourceFiles/overview/overview_layout.h @@ -38,7 +38,7 @@ class ItemBase; class AbstractItem : public LayoutItemBase { public: - virtual void paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const = 0; + virtual void paint(Painter &p, const QRect &clip, TextSelection selection, const PaintContext *context) const = 0; virtual ItemBase *toMediaItem() { return nullptr; @@ -164,7 +164,7 @@ public: Date(const QDate &date, bool month); void initDimensions() override; - void paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const override; + void paint(Painter &p, const QRect &clip, TextSelection selection, const PaintContext *context) const override; private: QDate _date; @@ -178,7 +178,7 @@ public: void initDimensions() override; int32 resizeGetHeight(int32 width) override; - void paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const override; + void paint(Painter &p, const QRect &clip, TextSelection selection, const PaintContext *context) const override; void getState(ClickHandlerPtr &link, HistoryCursorState &cursor, int x, int y) const override; private: @@ -196,7 +196,7 @@ public: void initDimensions() override; int32 resizeGetHeight(int32 width) override; - void paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const override; + void paint(Painter &p, const QRect &clip, TextSelection selection, const PaintContext *context) const override; void getState(ClickHandlerPtr &link, HistoryCursorState &cursor, int x, int y) const override; protected: @@ -229,7 +229,7 @@ public: Voice(DocumentData *voice, HistoryItem *parent); void initDimensions() override; - void paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const override; + void paint(Painter &p, const QRect &clip, TextSelection selection, const PaintContext *context) const override; void getState(ClickHandlerPtr &link, HistoryCursorState &cursor, int x, int y) const override; protected: @@ -263,7 +263,7 @@ public: Document(DocumentData *document, HistoryItem *parent); void initDimensions() override; - void paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const override; + void paint(Painter &p, const QRect &clip, TextSelection selection, const PaintContext *context) const override; void getState(ClickHandlerPtr &link, HistoryCursorState &cursor, int x, int y) const override; virtual DocumentData *getDocument() const override { @@ -308,7 +308,7 @@ public: void initDimensions() override; int32 resizeGetHeight(int32 width) override; - void paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const override; + void paint(Painter &p, const QRect &clip, TextSelection selection, const PaintContext *context) const override; void getState(ClickHandlerPtr &link, HistoryCursorState &cursor, int x, int y) const override; private: diff --git a/Telegram/SourceFiles/overviewwidget.cpp b/Telegram/SourceFiles/overviewwidget.cpp index 6d4a595be..6a7ffa811 100644 --- a/Telegram/SourceFiles/overviewwidget.cpp +++ b/Telegram/SourceFiles/overviewwidget.cpp @@ -566,8 +566,8 @@ void OverviewInner::dragActionFinish(const QPoint &screenPos, Qt::MouseButton bu if (_dragSelFrom && _dragSelTo) { applyDragSelection(); } else if (!_selected.isEmpty() && !_dragWasInactive) { - uint32 sel = _selected.cbegin().value(); - if (sel != FullSelection && (sel & 0xFFFF) == ((sel >> 16) & 0xFFFF)) { + auto sel = _selected.cbegin().value(); + if (sel != FullSelection && sel.from == sel.to) { _selected.clear(); App::main()->activate(); } @@ -782,7 +782,7 @@ bool OverviewInner::preloadLocal() { return true; } -uint32 OverviewInner::itemSelectedValue(int32 index) const { +TextSelection OverviewInner::itemSelectedValue(int32 index) const { int32 selfrom = -1, selto = -1; if (_dragSelFromIndex >= 0 && _dragSelToIndex >= 0) { selfrom = _dragSelToIndex; @@ -790,7 +790,7 @@ uint32 OverviewInner::itemSelectedValue(int32 index) const { } if (_items.at(index)->toMediaItem()) { // draw item if (index >= _dragSelToIndex && index <= _dragSelFromIndex && _dragSelToIndex >= 0) { - return (_dragSelecting && _items.at(index)->msgId() > 0) ? FullSelection : 0; + return (_dragSelecting && _items.at(index)->msgId() > 0) ? FullSelection : TextSelection{ 0, 0 }; } else if (!_selected.isEmpty()) { SelectedItems::const_iterator j = _selected.constFind(complexMsgId(_items.at(index)->getItem())); if (j != _selected.cend()) { @@ -798,7 +798,7 @@ uint32 OverviewInner::itemSelectedValue(int32 index) const { } } } - return 0; + return { 0, 0 }; } void OverviewInner::paintEvent(QPaintEvent *e) { @@ -1010,7 +1010,7 @@ void OverviewInner::onUpdateSelected() { if (_mousedItem == _dragItem && lnk && !_selected.isEmpty() && _selected.cbegin().value() != FullSelection) { bool afterSymbol = false, uponSymbol = false; uint16 second = 0; - _selected[_dragItem] = 0; + _selected[_dragItem] = { 0, 0 }; updateDragSelection(0, -1, 0, -1, false); } else if (canSelectMany) { bool selectingDown = (_reversed ? (_mousedItemIndex < _dragItemIndex) : (_mousedItemIndex > _dragItemIndex)) || (_mousedItemIndex == _dragItemIndex && ((_type == OverviewPhotos || _type == OverviewVideos) ? (_dragStartPos.x() < m.x()) : (_dragStartPos.y() < m.y()))); @@ -1729,7 +1729,7 @@ void OverviewInner::changingMsgId(HistoryItem *row, MsgId newId) { if (_selectedMsgId == oldId) _selectedMsgId = newId; for (SelectedItems::iterator i = _selected.begin(), e = _selected.end(); i != e; ++i) { if (i.key() == oldId) { - uint32 sel = i.value(); + auto sel = i.value(); _selected.erase(i); _selected.insert(newId, sel); break; diff --git a/Telegram/SourceFiles/overviewwidget.h b/Telegram/SourceFiles/overviewwidget.h index e0c039726..044744006 100644 --- a/Telegram/SourceFiles/overviewwidget.h +++ b/Telegram/SourceFiles/overviewwidget.h @@ -158,7 +158,7 @@ private: ChannelId _channel; bool _selMode; - uint32 itemSelectedValue(int32 index) const; + TextSelection itemSelectedValue(int32 index) const; int32 _rowsLeft, _rowWidth; @@ -209,7 +209,7 @@ private: // selection support, like in HistoryWidget Qt::CursorShape _cursor; HistoryCursorState _cursorState; - typedef QMap SelectedItems; + using SelectedItems = QMap; SelectedItems _selected; enum DragAction { NoDrag = 0x00, diff --git a/Telegram/SourceFiles/profilewidget.cpp b/Telegram/SourceFiles/profilewidget.cpp index ff4d80940..031c2bb27 100644 --- a/Telegram/SourceFiles/profilewidget.cpp +++ b/Telegram/SourceFiles/profilewidget.cpp @@ -1120,9 +1120,9 @@ void ProfileInner::updateSelected() { ClickHandlerPtr lnk; ClickHandlerHost *lnkhost = nullptr; - bool inText = false; if (!_about.isEmpty() && lp.y() >= _aboutTop && lp.y() < _aboutTop + _aboutHeight && lp.x() >= _left && lp.x() < _left + _width) { - _about.getState(lnk, inText, lp.x() - _left, lp.y() - _aboutTop, _width); + auto textState = _about.getState(lp.x() - _left, lp.y() - _aboutTop, _width); + lnk = textState.link; lnkhost = this; } ClickHandler::setActive(lnk, lnkhost); diff --git a/Telegram/SourceFiles/ui/flatlabel.cpp b/Telegram/SourceFiles/ui/flatlabel.cpp index 6daea1251..d170ee92d 100644 --- a/Telegram/SourceFiles/ui/flatlabel.cpp +++ b/Telegram/SourceFiles/ui/flatlabel.cpp @@ -110,10 +110,12 @@ void FlatLabel::updateHover() { QPoint m(mapFromGlobal(_lastMousePos)); textstyleSet(&_tst); - ClickHandlerPtr handler = _text.link(m.x(), m.y(), width(), _st.align); + Text::StateRequest request; + request.align = _st.align; + auto state = _text.getState(m.x(), m.y(), width(), request); textstyleRestore(); - ClickHandler::setActive(handler, this); + ClickHandler::setActive(state.link, this); } void FlatLabel::setOpacity(float64 o) { diff --git a/Telegram/SourceFiles/ui/text.cpp b/Telegram/SourceFiles/ui/text.cpp index 190da42ce..b47ae3388 100644 --- a/Telegram/SourceFiles/ui/text.cpp +++ b/Telegram/SourceFiles/ui/text.cpp @@ -923,7 +923,7 @@ public: return _blockEnd(t, i, e) - (*i)->from(); } - TextPainter(QPainter *p, const Text *t) : _p(p), _t(t), _elideLast(false), _breakEverywhere(false), _elideRemoveFromEnd(0), _str(0), _elideSavedBlock(0), _lnkResult(0), _inTextFlag(0), _getSymbol(0), _getSymbolAfter(0), _getSymbolUpon(0) { + TextPainter(QPainter *p, const Text *t) : _p(p), _t(t) { } void initNextParagraph(Text::TextBlocks::const_iterator i) { @@ -987,7 +987,7 @@ public: } } - void draw(int32 left, int32 top, int32 w, style::align align, int32 yFrom, int32 yTo, uint16 selectedFrom = 0, uint16 selectedTo = 0) { + void draw(int32 left, int32 top, int32 w, style::align align, int32 yFrom, int32 yTo, TextSelection selection = { 0, 0 }) { if (_t->isEmpty()) return; _blocksSize = _t->_blocks.size(); @@ -1005,8 +1005,7 @@ public: if (_elideLast) { _yToElide = _yTo; } - _selectedFrom = selectedFrom; - _selectedTo = selectedTo; + _selection = selection; _wLeft = _w = w; _str = _t->_text.unicode(); @@ -1173,14 +1172,13 @@ public: if (_lineStart < _t->_text.size()) { if (!drawLine(_t->_text.size(), e, e)) return; } - if (_getSymbol) { - *_getSymbol = _t->_text.size(); - *_getSymbolAfter = false; - *_getSymbolUpon = false; + if (!_p && _lookupSymbol) { + _lookupResult.symbol = _t->_text.size(); + _lookupResult.afterSymbol = false; } } - void drawElided(int32 left, int32 top, int32 w, style::align align, int32 lines, int32 yFrom, int32 yTo, int32 removeFromEnd, bool breakEverywhere) { + void drawElided(int32 left, int32 top, int32 w, style::align align, int32 lines, int32 yFrom, int32 yTo, int32 removeFromEnd, bool breakEverywhere, TextSelection selection) { if (lines <= 0 || _t->isNull()) return; if (yTo < 0 || (lines - 1) * _t->_font->height < yTo) { @@ -1189,48 +1187,45 @@ public: _elideRemoveFromEnd = removeFromEnd; } _breakEverywhere = breakEverywhere; - draw(left, top, w, align, yFrom, yTo); + draw(left, top, w, align, yFrom, yTo, selection); } - const ClickHandlerPtr &link(int32 x, int32 y, int32 w, style::align align) { - static StaticNeverFreedPointer zero(new ClickHandlerPtr()); - - _lnkX = x; - _lnkY = y; - _lnkResult = zero.data(); - if (!_t->isNull() && _lnkX >= 0 && _lnkX < w && _lnkY >= 0) { - draw(0, 0, w, align, _lnkY, _lnkY + 1); - } - return *_lnkResult; - } - - void getState(ClickHandlerPtr &lnk, bool &inText, int32 x, int32 y, int32 w, style::align align, bool breakEverywhere) { - lnk.clear(); - inText = false; - - if (!_t->isNull() && x >= 0 && x < w && y >= 0) { - _lnkX = x; - _lnkY = y; - _lnkResult = &lnk; - _inTextFlag = &inText; - _breakEverywhere = breakEverywhere; - draw(0, 0, w, align, _lnkY, _lnkY + 1); - lnk = *_lnkResult; - } - } - - void getSymbol(uint16 &symbol, bool &after, bool &upon, int32 x, int32 y, int32 w, style::align align) { - symbol = 0; - after = false; - upon = false; + Text::StateResult getState(int x, int y, int w, Text::StateRequest request) { if (!_t->isNull() && y >= 0) { - _lnkX = x; - _lnkY = y; - _getSymbol = &symbol; - _getSymbolAfter = &after; - _getSymbolUpon = &upon; - draw(0, 0, w, align, _lnkY, _lnkY + 1); + _lookupRequest = request; + _lookupX = x; + _lookupY = y; + + _breakEverywhere = (_lookupRequest.flags & Text::StateRequest::Flag::BreakEverywhere); + _lookupSymbol = (_lookupRequest.flags & Text::StateRequest::Flag::LookupSymbol); + _lookupLink = (_lookupRequest.flags & Text::StateRequest::Flag::LookupLink); + if (_lookupSymbol || (_lookupX >= 0 && _lookupX < w)) { + draw(0, 0, w, _lookupRequest.align, _lookupY, _lookupY + 1); + } } + return _lookupResult; + } + + Text::StateResult getStateElided(int x, int y, int w, Text::StateRequestElided request) { + if (!_t->isNull() && y >= 0 && request.lines > 0) { + _lookupRequest = request; + _lookupX = x; + _lookupY = y; + + _breakEverywhere = (_lookupRequest.flags & Text::StateRequest::Flag::BreakEverywhere); + _lookupSymbol = (_lookupRequest.flags & Text::StateRequest::Flag::LookupSymbol); + _lookupLink = (_lookupRequest.flags & Text::StateRequest::Flag::LookupLink); + if (_lookupSymbol || (_lookupX >= 0 && _lookupX < w)) { + int yTo = _lookupY + 1; + if (yTo < 0 || (request.lines - 1) * _t->_font->height < yTo) { + yTo = request.lines * _t->_font->height; + _elideLast = true; + _elideRemoveFromEnd = request.removeFromEnd; + } + draw(0, 0, w, _lookupRequest.align, _lookupY, _lookupY + 1); + } + } + return _lookupResult; } const QPen &blockPen(ITextBlock *block) { @@ -1288,34 +1283,44 @@ public: x += _wLeft; } - if (_getSymbol) { - if (_lnkX < x) { - if (_parDirection == Qt::RightToLeft) { - *_getSymbol = (_lineEnd > _lineStart) ? (_lineEnd - 1) : _lineStart; - *_getSymbolAfter = (_lineEnd > _lineStart) ? true : false; - *_getSymbolUpon = ((_lnkX >= _x) && (_lineEnd < _t->_text.size()) && (!_endBlock || _endBlock->type() != TextBlockTSkip)) ? true : false; - } else { - *_getSymbol = _lineStart; - *_getSymbolAfter = false; - *_getSymbolUpon = ((_lnkX >= _x) && (_lineStart > 0)) ? true : false; + if (!_p) { + if (_lookupX < x) { + if (_lookupSymbol) { + if (_parDirection == Qt::RightToLeft) { + _lookupResult.symbol = (_lineEnd > _lineStart) ? (_lineEnd - 1) : _lineStart; + _lookupResult.afterSymbol = (_lineEnd > _lineStart) ? true : false; +// _lookupResult.uponSymbol = ((_lookupX >= _x) && (_lineEnd < _t->_text.size()) && (!_endBlock || _endBlock->type() != TextBlockTSkip)) ? true : false; + } else { + _lookupResult.symbol = _lineStart; + _lookupResult.afterSymbol = false; +// _lookupResult.uponSymbol = ((_lookupX >= _x) && (_lineStart > 0)) ? true : false; + } } + if (_lookupLink) { + _lookupResult.link.clear(); + } + _lookupResult.uponSymbol = false; return false; - } else if (_lnkX >= x + (_w - _wLeft)) { + } else if (_lookupX >= x + (_w - _wLeft)) { if (_parDirection == Qt::RightToLeft) { - *_getSymbol = _lineStart; - *_getSymbolAfter = false; - *_getSymbolUpon = ((_lnkX < _x + _w) && (_lineStart > 0)) ? true : false; + _lookupResult.symbol = _lineStart; + _lookupResult.afterSymbol = false; +// _lookupResult.uponSymbol = ((_lookupX < _x + _w) && (_lineStart > 0)) ? true : false; } else { - *_getSymbol = (_lineEnd > _lineStart) ? (_lineEnd - 1) : _lineStart; - *_getSymbolAfter = (_lineEnd > _lineStart) ? true : false; - *_getSymbolUpon = ((_lnkX < _x + _w) && (_lineEnd < _t->_text.size()) && (!_endBlock || _endBlock->type() != TextBlockTSkip)) ? true : false; + _lookupResult.symbol = (_lineEnd > _lineStart) ? (_lineEnd - 1) : _lineStart; + _lookupResult.afterSymbol = (_lineEnd > _lineStart) ? true : false; +// _lookupResult.uponSymbol = ((_lookupX < _x + _w) && (_lineEnd < _t->_text.size()) && (!_endBlock || _endBlock->type() != TextBlockTSkip)) ? true : false; } + if (_lookupLink) { + _lookupResult.link.clear(); + } + _lookupResult.uponSymbol = false; return false; } } - bool selectFromStart = (_selectedTo > _lineStart) && (_lineStart > 0) && (_selectedFrom <= _lineStart); - bool selectTillEnd = (_selectedTo >= _lineEnd) && (_lineEnd < _t->_text.size()) && (_selectedFrom < _lineEnd) && (!_endBlock || _endBlock->type() != TextBlockTSkip); + bool selectFromStart = (_selection.to > _lineStart) && (_lineStart > 0) && (_selection.from <= _lineStart); + bool selectTillEnd = (_selection.to >= _lineEnd) && (_lineEnd < _t->_text.size()) && (_selection.from < _lineEnd) && (!_endBlock || _endBlock->type() != TextBlockTSkip); if ((selectFromStart && _parDirection == Qt::LeftToRight) || (selectTillEnd && _parDirection == Qt::RightToLeft)) { if (x > _x) { @@ -1409,52 +1414,48 @@ public: } if (si.analysis.flags >= QScriptAnalysis::TabOrObject) { TextBlockType _type = currentBlock->type(); - if (_lnkResult && _lnkX >= x && _lnkX < x + si.width) { - if (currentBlock->lnkIndex() && _lnkY >= _y + _yDelta && _lnkY < _y + _yDelta + _fontHeight) { - _lnkResult = &_t->_links.at(currentBlock->lnkIndex() - 1); - } - if (_inTextFlag && _type != TextBlockTSkip) { - *_inTextFlag = true; - } - return false; - } else if (_getSymbol && _lnkX >= x && _lnkX < x + si.width) { - if (_type == TextBlockTSkip) { - if (_parDirection == Qt::RightToLeft) { - *_getSymbol = _lineStart; - *_getSymbolAfter = false; - *_getSymbolUpon = false; - } else { - *_getSymbol = (trimmedLineEnd > _lineStart) ? (trimmedLineEnd - 1) : _lineStart; - *_getSymbolAfter = (trimmedLineEnd > _lineStart) ? true : false; - *_getSymbolUpon = false; + if (!_p && _lookupX >= x && _lookupX < x + si.width) { // _lookupRequest + if (_lookupLink) { + if (currentBlock->lnkIndex() && _lookupY >= _y + _yDelta && _lookupY < _y + _yDelta + _fontHeight) { + _lookupResult.link = _t->_links.at(currentBlock->lnkIndex() - 1); } - return false; } - const QChar *chFrom = _str + currentBlock->from(), *chTo = chFrom + ((nextBlock ? nextBlock->from() : _t->_text.size()) - currentBlock->from()); - if (chTo > chFrom && (chTo - 1)->unicode() == QChar::Space) { - if (rtl) { - if (_lnkX < x + (si.width - currentBlock->f_width())) { - *_getSymbol = (chTo - 1 - _str); // up to ending space, included, rtl - *_getSymbolAfter = (_lnkX < x + (si.width - currentBlock->f_width()) / 2) ? true : false; - *_getSymbolUpon = true; - return false; + if (_type != TextBlockTSkip) { + _lookupResult.uponSymbol = true; + } + if (_lookupSymbol) { + if (_type == TextBlockTSkip) { + if (_parDirection == Qt::RightToLeft) { + _lookupResult.symbol = _lineStart; + _lookupResult.afterSymbol = false; + } else { + _lookupResult.symbol = (trimmedLineEnd > _lineStart) ? (trimmedLineEnd - 1) : _lineStart; + _lookupResult.afterSymbol = (trimmedLineEnd > _lineStart) ? true : false; } - } else if (_lnkX >= x + currentBlock->f_width()) { - *_getSymbol = (chTo - 1 - _str); // up to ending space, inclided, ltr - *_getSymbolAfter = (_lnkX >= x + currentBlock->f_width() + (currentBlock->f_rpadding() / 2)) ? true : false; - *_getSymbolUpon = true; return false; } - --chTo; - } - if (_lnkX < x + (rtl ? (si.width - currentBlock->f_width()) : 0) + (currentBlock->f_width() / 2)) { - *_getSymbol = ((rtl && chTo > chFrom) ? (chTo - 1) : chFrom) - _str; - *_getSymbolAfter = (rtl && chTo > chFrom) ? true : false; - *_getSymbolUpon = true; - } else { - *_getSymbol = ((rtl || chTo <= chFrom) ? chFrom : (chTo - 1)) - _str; - *_getSymbolAfter = (rtl || chTo <= chFrom) ? false : true; - *_getSymbolUpon = true; + const QChar *chFrom = _str + currentBlock->from(), *chTo = chFrom + ((nextBlock ? nextBlock->from() : _t->_text.size()) - currentBlock->from()); + if (chTo > chFrom && (chTo - 1)->unicode() == QChar::Space) { + if (rtl) { + if (_lookupX < x + (si.width - currentBlock->f_width())) { + _lookupResult.symbol = (chTo - 1 - _str); // up to ending space, included, rtl + _lookupResult.afterSymbol = (_lookupX < x + (si.width - currentBlock->f_width()) / 2) ? true : false; + return false; + } + } else if (_lookupX >= x + currentBlock->f_width()) { + _lookupResult.symbol = (chTo - 1 - _str); // up to ending space, inclided, ltr + _lookupResult.afterSymbol = (_lookupX >= x + currentBlock->f_width() + (currentBlock->f_rpadding() / 2)) ? true : false; + return false; + } + --chTo; + } + if (_lookupX < x + (rtl ? (si.width - currentBlock->f_width()) : 0) + (currentBlock->f_width() / 2)) { + _lookupResult.symbol = ((rtl && chTo > chFrom) ? (chTo - 1) : chFrom) - _str; + _lookupResult.afterSymbol = (rtl && chTo > chFrom) ? true : false; + } else { + _lookupResult.symbol = ((rtl || chTo <= chFrom) ? chFrom : (chTo - 1)) - _str; + _lookupResult.afterSymbol = (rtl || chTo <= chFrom) ? false : true; + } } return false; } else if (_p && _type == TextBlockTEmoji) { @@ -1462,15 +1463,15 @@ public: if (rtl) { glyphX += (si.width - currentBlock->f_width()); } - if (_localFrom + si.position < _selectedTo) { + if (_localFrom + si.position < _selection.to) { const QChar *chFrom = _str + currentBlock->from(), *chTo = chFrom + ((nextBlock ? nextBlock->from() : _t->_text.size()) - currentBlock->from()); - if (_localFrom + si.position >= _selectedFrom) { // could be without space - if (chTo == chFrom || (chTo - 1)->unicode() != QChar::Space || _selectedTo >= (chTo - _str)) { + if (_localFrom + si.position >= _selection.from) { // could be without space + if (chTo == chFrom || (chTo - 1)->unicode() != QChar::Space || _selection.to >= (chTo - _str)) { _p->fillRect(QRectF(x.toReal(), _y + _yDelta, si.width.toReal(), _fontHeight), _textStyle->selectBg->b); } else { // or with space _p->fillRect(QRectF(glyphX.toReal(), _y + _yDelta, currentBlock->f_width().toReal(), _fontHeight), _textStyle->selectBg->b); } - } else if (chTo > chFrom && (chTo - 1)->unicode() == QChar::Space && (chTo - 1 - _str) >= _selectedFrom) { + } else if (chTo > chFrom && (chTo - 1)->unicode() == QChar::Space && (chTo - 1 - _str) >= _selection.from) { if (rtl) { // rtl space only _p->fillRect(QRectF(x.toReal(), _y + _yDelta, (glyphX - x).toReal(), _fontHeight), _textStyle->selectBg->b); } else { // ltr space only @@ -1504,54 +1505,52 @@ public: for (int g = glyphsStart; g < glyphsEnd; ++g) itemWidth += glyphs.effectiveAdvance(g); - if (_lnkResult && _lnkX >= x && _lnkX < x + itemWidth) { - if (currentBlock->lnkIndex() && _lnkY >= _y + _yDelta && _lnkY < _y + _yDelta + _fontHeight) { - _lnkResult = &_t->_links.at(currentBlock->lnkIndex() - 1); - } - if (_inTextFlag) { - *_inTextFlag = true; - } - return false; - } else if (_getSymbol && _lnkX >= x && _lnkX < x + itemWidth) { - QFixed tmpx = rtl ? (x + itemWidth) : x; - for (int ch = 0, g, itemL = itemEnd - itemStart; ch < itemL;) { - g = logClusters[itemStart - si.position + ch]; - QFixed gwidth = glyphs.effectiveAdvance(g); - // ch2 - glyph end, ch - glyph start, (ch2 - ch) - how much chars it takes - int ch2 = ch + 1; - while ((ch2 < itemL) && (g == logClusters[itemStart - si.position + ch2])) { - ++ch2; + if (!_p && _lookupX >= x && _lookupX < x + itemWidth) { // _lookupRequest + if (_lookupLink) { + if (currentBlock->lnkIndex() && _lookupY >= _y + _yDelta && _lookupY < _y + _yDelta + _fontHeight) { + _lookupResult.link = _t->_links.at(currentBlock->lnkIndex() - 1); } - for (int charsCount = (ch2 - ch); ch < ch2; ++ch) { - QFixed shift1 = QFixed(2 * (charsCount - (ch2 - ch)) + 2) * gwidth / QFixed(2 * charsCount), - shift2 = QFixed(2 * (charsCount - (ch2 - ch)) + 1) * gwidth / QFixed(2 * charsCount); - if ((rtl && _lnkX >= tmpx - shift1) || - (!rtl && _lnkX < tmpx + shift1)) { - *_getSymbol = _localFrom + itemStart + ch; - if ((rtl && _lnkX >= tmpx - shift2) || - (!rtl && _lnkX < tmpx + shift2)) { - *_getSymbolAfter = false; - } else { - *_getSymbolAfter = true; + } + _lookupResult.uponSymbol = true; + if (_lookupSymbol) { + QFixed tmpx = rtl ? (x + itemWidth) : x; + for (int ch = 0, g, itemL = itemEnd - itemStart; ch < itemL;) { + g = logClusters[itemStart - si.position + ch]; + QFixed gwidth = glyphs.effectiveAdvance(g); + // ch2 - glyph end, ch - glyph start, (ch2 - ch) - how much chars it takes + int ch2 = ch + 1; + while ((ch2 < itemL) && (g == logClusters[itemStart - si.position + ch2])) { + ++ch2; + } + for (int charsCount = (ch2 - ch); ch < ch2; ++ch) { + QFixed shift1 = QFixed(2 * (charsCount - (ch2 - ch)) + 2) * gwidth / QFixed(2 * charsCount), + shift2 = QFixed(2 * (charsCount - (ch2 - ch)) + 1) * gwidth / QFixed(2 * charsCount); + if ((rtl && _lookupX >= tmpx - shift1) || + (!rtl && _lookupX < tmpx + shift1)) { + _lookupResult.symbol = _localFrom + itemStart + ch; + if ((rtl && _lookupX >= tmpx - shift2) || + (!rtl && _lookupX < tmpx + shift2)) { + _lookupResult.afterSymbol = false; + } else { + _lookupResult.afterSymbol = true; + } + return false; } - *_getSymbolUpon = true; - return false; + } + if (rtl) { + tmpx -= gwidth; + } else { + tmpx += gwidth; } } - if (rtl) { - tmpx -= gwidth; + if (itemEnd > itemStart) { + _lookupResult.symbol = _localFrom + itemEnd - 1; + _lookupResult.afterSymbol = true; } else { - tmpx += gwidth; + _lookupResult.symbol = _localFrom + itemStart; + _lookupResult.afterSymbol = false; } } - if (itemEnd > itemStart) { - *_getSymbol = _localFrom + itemEnd - 1; - *_getSymbolAfter = true; - } else { - *_getSymbol = _localFrom + itemStart; - *_getSymbolAfter = false; - } - *_getSymbolUpon = true; return false; } else if (_p) { #ifndef TDESKTOP_WINRT // temp @@ -1564,12 +1563,12 @@ public: gf.justified = false; gf.initWithScriptItem(si); #endif // !TDESKTOP_WINRT - if (_localFrom + itemStart < _selectedTo && _localFrom + itemEnd > _selectedFrom) { + if (_localFrom + itemStart < _selection.to && _localFrom + itemEnd > _selection.from) { QFixed selX = x, selWidth = itemWidth; - if (_localFrom + itemEnd > _selectedTo || _localFrom + itemStart < _selectedFrom) { + if (_localFrom + itemEnd > _selection.to || _localFrom + itemStart < _selection.from) { selWidth = 0; int itemL = itemEnd - itemStart; - int selStart = _selectedFrom - (_localFrom + itemStart), selEnd = _selectedTo - (_localFrom + itemStart); + int selStart = _selection.from - (_localFrom + itemStart), selEnd = _selection.to - (_localFrom + itemStart); if (selStart < 0) selStart = 0; if (selEnd > itemL) selEnd = itemL; for (int ch = 0, g; ch < selEnd;) { @@ -1675,6 +1674,7 @@ public: if (_wLeft < si.width) { lineText = lineText.mid(0, currentBlock->from() - _localFrom) + _Elide; lineLength = currentBlock->from() + _Elide.size() - _lineStart; + _selection.to = std::min({ _selection.to, currentBlock->from() }); setElideBidi(currentBlock->from(), _Elide.size()); elideSaveBlock(blockIndex - 1, _endBlock, currentBlock->from(), elideWidth); return; @@ -1706,6 +1706,7 @@ public: if (lineText.size() <= pos || repeat > 3) { lineText += _Elide; lineLength = _localFrom + pos + _Elide.size() - _lineStart; + _selection.to = std::min({ _selection.to, uint16(_localFrom + pos) }); setElideBidi(_localFrom + pos, _Elide.size()); _blocksSize = blockIndex; _endBlock = nextBlock; @@ -1724,7 +1725,8 @@ public: } } - int32 elideStart = _lineStart + lineText.length(); + int32 elideStart = _localFrom + lineText.size(); + _selection.to = std::min({ _selection.to, uint16(elideStart) }); setElideBidi(elideStart, _Elide.size()); lineText += _Elide; @@ -2369,13 +2371,14 @@ private: QPainter *_p; const Text *_t; - bool _elideLast, _breakEverywhere; - int32 _elideRemoveFromEnd; + bool _elideLast = false; + bool _breakEverywhere = false; + int32 _elideRemoveFromEnd = 0; style::align _align; QPen _originalPen; int32 _yFrom, _yTo, _yToElide; - uint16 _selectedFrom, _selectedTo; - const QChar *_str; + TextSelection _selection = { 0, 0 }; + const QChar *_str = nullptr; // current paragraph data Text::TextBlocks::const_iterator _parStartBlock; @@ -2393,18 +2396,18 @@ private: // elided hack support int32 _blocksSize; int32 _elideSavedIndex; - ITextBlock *_elideSavedBlock; + ITextBlock *_elideSavedBlock = nullptr; int32 _lineStart, _localFrom; int32 _lineStartBlock; // link and symbol resolve - QFixed _lnkX; - int32 _lnkY; - const ClickHandlerPtr *_lnkResult; - bool *_inTextFlag; - uint16 *_getSymbol; - bool *_getSymbolAfter, *_getSymbolUpon; + QFixed _lookupX = 0; + int _lookupY = 0; + bool _lookupSymbol = false; + bool _lookupLink = false; + Text::StateRequest _lookupRequest; + Text::StateResult _lookupResult; }; @@ -2935,36 +2938,32 @@ void Text::replaceFont(style::font f) { _font = f; } -void Text::draw(QPainter &painter, int32 left, int32 top, int32 w, style::align align, int32 yFrom, int32 yTo, uint16 selectedFrom, uint16 selectedTo) const { +void Text::draw(QPainter &painter, int32 left, int32 top, int32 w, style::align align, int32 yFrom, int32 yTo, TextSelection selection) const { // painter.fillRect(QRect(left, top, w, countHeight(w)), QColor(0, 0, 0, 32)); // debug TextPainter p(&painter, this); - p.draw(left, top, w, align, yFrom, yTo, selectedFrom, selectedTo); + p.draw(left, top, w, align, yFrom, yTo, selection); } -void Text::drawElided(QPainter &painter, int32 left, int32 top, int32 w, int32 lines, style::align align, int32 yFrom, int32 yTo, int32 removeFromEnd, bool breakEverywhere) const { +void Text::drawElided(QPainter &painter, int32 left, int32 top, int32 w, int32 lines, style::align align, int32 yFrom, int32 yTo, int32 removeFromEnd, bool breakEverywhere, TextSelection selection) const { // painter.fillRect(QRect(left, top, w, countHeight(w)), QColor(0, 0, 0, 32)); // debug TextPainter p(&painter, this); - p.drawElided(left, top, w, align, lines, yFrom, yTo, removeFromEnd, breakEverywhere); + p.drawElided(left, top, w, align, lines, yFrom, yTo, removeFromEnd, breakEverywhere, selection); } -const ClickHandlerPtr &Text::link(int32 x, int32 y, int32 width, style::align align) const { +Text::StateResult Text::getState(int x, int y, int width, StateRequest request) const { TextPainter p(0, this); - return p.link(x, y, width, align); + return p.getState(x, y, width, request); } -void Text::getState(ClickHandlerPtr &lnk, bool &inText, int32 x, int32 y, int32 width, style::align align, bool breakEverywhere) const { +Text::StateResult Text::getStateElided(int x, int y, int width, StateRequestElided request) const { TextPainter p(0, this); - p.getState(lnk, inText, x, y, width, align, breakEverywhere); + return p.getStateElided(x, y, width, request); } -void Text::getSymbol(uint16 &symbol, bool &after, bool &upon, int32 x, int32 y, int32 width, style::align align) const { - TextPainter p(0, this); - p.getSymbol(symbol, after, upon, x, y, width, align); -} - -uint32 Text::adjustSelection(uint16 from, uint16 to, TextSelectType selectType) const { +TextSelection Text::adjustSelection(TextSelection selection, TextSelectType selectType) const { + uint16 from = selection.from, to = selection.to; if (from < _text.size() && from <= to) { - if (to > _text.size()) to = _text.size() - 1; + if (to > _text.size()) to = _text.size(); if (selectType == TextSelectParagraphs) { if (!chIsParagraphSeparator(_text.at(from))) { while (from > 0 && !chIsParagraphSeparator(_text.at(from - 1))) { @@ -2997,10 +2996,10 @@ uint32 Text::adjustSelection(uint16 from, uint16 to, TextSelectType selectType) } } } - return (from << 16) | to; + return { from, to }; } -QString Text::original(uint16 selectedFrom, uint16 selectedTo, ExpandLinksMode mode) const { +QString Text::original(TextSelection selection, ExpandLinksMode mode) const { QString result, emptyurl; result.reserve(_text.size()); @@ -3013,7 +3012,7 @@ QString Text::original(uint16 selectedFrom, uint16 selectedTo, ExpandLinksMode m const ClickHandlerPtr &lnk(_links.at(lnkIndex - 1)); const QString &url = (mode == ExpandLinksNone || !lnk) ? emptyurl : lnk->text(); - int32 rangeFrom = qMax(int32(selectedFrom), lnkFrom), rangeTo = qMin(blockFrom, int32(selectedTo)); + int32 rangeFrom = qMax(int32(selection.from), lnkFrom), rangeTo = qMin(blockFrom, int32(selection.to)); if (rangeTo > rangeFrom) { QStringRef r = _text.midRef(rangeFrom, rangeTo - rangeFrom); @@ -3043,7 +3042,7 @@ QString Text::original(uint16 selectedFrom, uint16 selectedTo, ExpandLinksMode m if (type == TextBlockTSkip) continue; if (!blockLnkIndex) { - int32 rangeFrom = qMax(selectedFrom, (*i)->from()), rangeTo = qMin(selectedTo, uint16((*i)->from() + TextPainter::_blockLength(this, i, e))); + 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); } diff --git a/Telegram/SourceFiles/ui/text.h b/Telegram/SourceFiles/ui/text.h index c890745f5..6f73c5dae 100644 --- a/Telegram/SourceFiles/ui/text.h +++ b/Telegram/SourceFiles/ui/text.h @@ -344,6 +344,23 @@ enum TextSelectType { TextSelectParagraphs = 0x03, }; +struct TextSelection { + constexpr TextSelection() : from(0), to(0) { + } + constexpr TextSelection(uint16 from, uint16 to) : from(from), to(to) { + } + uint16 from : 16; + uint16 to : 16; +}; +inline bool operator==(TextSelection a, TextSelection b) { + return a.from == b.from && a.to == b.to; +} +inline bool operator!=(TextSelection a, TextSelection b) { + return !(a == b); +} + +static constexpr TextSelection AllTextSelection = { 0, 0xFFFF }; + typedef QPair TextCustomTag; // open str and close str typedef QMap TextCustomTagsMap; @@ -381,34 +398,55 @@ public: void replaceFont(style::font f); // does not recount anything, use at your own risk! - void draw(QPainter &p, int32 left, int32 top, int32 width, style::align align = style::al_left, int32 yFrom = 0, int32 yTo = -1, uint16 selectedFrom = 0, uint16 selectedTo = 0) const; - void drawElided(QPainter &p, int32 left, int32 top, int32 width, int32 lines = 1, style::align align = style::al_left, int32 yFrom = 0, int32 yTo = -1, int32 removeFromEnd = 0, bool breakEverywhere = false) const; - void drawLeft(QPainter &p, int32 left, int32 top, int32 width, int32 outerw, style::align align = style::al_left, int32 yFrom = 0, int32 yTo = -1, uint16 selectedFrom = 0, uint16 selectedTo = 0) const { - draw(p, rtl() ? (outerw - left - width) : left, top, width, align, yFrom, yTo, selectedFrom, selectedTo); + void draw(QPainter &p, int32 left, int32 top, int32 width, style::align align = style::al_left, int32 yFrom = 0, int32 yTo = -1, TextSelection selection = { 0, 0 }) const; + void drawElided(QPainter &p, int32 left, int32 top, int32 width, int32 lines = 1, style::align align = style::al_left, int32 yFrom = 0, int32 yTo = -1, int32 removeFromEnd = 0, bool breakEverywhere = false, TextSelection selection = { 0, 0 }) const; + void drawLeft(QPainter &p, int32 left, int32 top, int32 width, int32 outerw, style::align align = style::al_left, int32 yFrom = 0, int32 yTo = -1, TextSelection selection = { 0, 0 }) const { + draw(p, rtl() ? (outerw - left - width) : left, top, width, align, yFrom, yTo, selection); } - void drawLeftElided(QPainter &p, int32 left, int32 top, int32 width, int32 outerw, int32 lines = 1, style::align align = style::al_left, int32 yFrom = 0, int32 yTo = -1, int32 removeFromEnd = 0, bool breakEverywhere = false) const { - drawElided(p, rtl() ? (outerw - left - width) : left, top, width, lines, align, yFrom, yTo, removeFromEnd, breakEverywhere); + void drawLeftElided(QPainter &p, int32 left, int32 top, int32 width, int32 outerw, int32 lines = 1, style::align align = style::al_left, int32 yFrom = 0, int32 yTo = -1, int32 removeFromEnd = 0, bool breakEverywhere = false, TextSelection selection = { 0, 0 }) const { + drawElided(p, rtl() ? (outerw - left - width) : left, top, width, lines, align, yFrom, yTo, removeFromEnd, breakEverywhere, selection); } - void drawRight(QPainter &p, int32 right, int32 top, int32 width, int32 outerw, style::align align = style::al_left, int32 yFrom = 0, int32 yTo = -1, uint16 selectedFrom = 0, uint16 selectedTo = 0) const { - draw(p, rtl() ? right : (outerw - right - width), top, width, align, yFrom, yTo, selectedFrom, selectedTo); + void drawRight(QPainter &p, int32 right, int32 top, int32 width, int32 outerw, style::align align = style::al_left, int32 yFrom = 0, int32 yTo = -1, TextSelection selection = { 0, 0 }) const { + draw(p, rtl() ? right : (outerw - right - width), top, width, align, yFrom, yTo, selection); } - void drawRightElided(QPainter &p, int32 right, int32 top, int32 width, int32 outerw, int32 lines = 1, style::align align = style::al_left, int32 yFrom = 0, int32 yTo = -1, int32 removeFromEnd = 0, bool breakEverywhere = false) const { - drawElided(p, rtl() ? right : (outerw - right - width), top, width, lines, align, yFrom, yTo, removeFromEnd, breakEverywhere); + void drawRightElided(QPainter &p, int32 right, int32 top, int32 width, int32 outerw, int32 lines = 1, style::align align = style::al_left, int32 yFrom = 0, int32 yTo = -1, int32 removeFromEnd = 0, bool breakEverywhere = false, TextSelection selection = { 0, 0 }) const { + drawElided(p, rtl() ? right : (outerw - right - width), top, width, lines, align, yFrom, yTo, removeFromEnd, breakEverywhere, selection); } - const ClickHandlerPtr &link(int32 x, int32 y, int32 width, style::align align = style::al_left) const; - const ClickHandlerPtr &linkLeft(int32 x, int32 y, int32 width, int32 outerw, style::align align = style::al_left) const { - return link(rtl() ? (outerw - x - width) : x, y, width, align); + struct StateRequest { + enum class Flag { + BreakEverywhere = 0x01, + LookupSymbol = 0x02, + LookupLink = 0x04, + }; + Q_DECLARE_FLAGS(Flags, Flag); + + style::align align = style::al_left; + Flags flags = Flag::LookupLink; + }; + struct StateResult { + ClickHandlerPtr link; + bool uponSymbol = false; + bool afterSymbol = false; + uint16 symbol = 0; + }; + StateResult getState(int x, int y, int width, StateRequest request = StateRequest()) const; + StateResult getStateLeft(int x, int y, int width, int outerw, StateRequest request = StateRequest()) const { + return getState(rtl() ? (outerw - x - width) : x, y, width, request); } - void getState(ClickHandlerPtr &lnk, bool &inText, int32 x, int32 y, int32 width, style::align align = style::al_left, bool breakEverywhere = false) const; - void getStateLeft(ClickHandlerPtr &lnk, bool &inText, int32 x, int32 y, int32 width, int32 outerw, style::align align = style::al_left, bool breakEverywhere = false) const { - return getState(lnk, inText, rtl() ? (outerw - x - width) : x, y, width, align, breakEverywhere); + struct StateRequestElided : public StateRequest { + StateRequestElided() = default; + StateRequestElided(const StateRequest &other) : StateRequest(other) { + } + int lines = 1; + int removeFromEnd = 0; + }; + StateResult getStateElided(int x, int y, int width, StateRequestElided request = StateRequestElided()) const; + StateResult getStateElidedLeft(int x, int y, int width, int outerw, StateRequestElided request = StateRequestElided()) const { + return getStateElided(rtl() ? (outerw - x - width) : x, y, width, request); } - void getSymbol(uint16 &symbol, bool &after, bool &upon, int32 x, int32 y, int32 width, style::align align = style::al_left) const; - void getSymbolLeft(uint16 &symbol, bool &after, bool &upon, int32 x, int32 y, int32 width, int32 outerw, style::align align = style::al_left) const { - return getSymbol(symbol, after, upon, rtl() ? (outerw - x - width) : x, y, width, align); - } - uint32 adjustSelection(uint16 from, uint16 to, TextSelectType selectType) const; + + TextSelection adjustSelection(TextSelection selection, TextSelectType selectType) const; bool isEmpty() const { return _text.isEmpty(); @@ -416,12 +454,15 @@ public: bool isNull() const { return !_font; } + int length() const { + return _text.size(); + } enum ExpandLinksMode { ExpandLinksNone, ExpandLinksShortened, ExpandLinksAll, }; - QString original(uint16 selectedFrom = 0, uint16 selectedTo = 0xFFFF, ExpandLinksMode mode = ExpandLinksShortened) const; + QString original(TextSelection selection = { 0, 0xFFFF }, ExpandLinksMode mode = ExpandLinksShortened) const; EntitiesInText originalEntities() const; bool lastDots(int32 dots, int32 maxdots = 3) { // hack for typing animation @@ -474,6 +515,17 @@ private: friend class TextPainter; }; +inline TextSelection snapSelection(int from, int to) { + return { static_cast(snap(from, 0, 0xFFFF)), static_cast(snap(to, 0, 0xFFFF)) }; +} +inline TextSelection shiftSelection(TextSelection selection, const Text &byText) { + int len = byText.length(); + return snapSelection(int(selection.from) + len, int(selection.to) + len); +} +inline TextSelection unshiftSelection(TextSelection selection, const Text &byText) { + int len = byText.length(); + return snapSelection(int(selection.from) - len, int(selection.to) - len); +} void initLinkSets(); const QSet &validProtocols();