diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index b831c8ea3..552905840 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -2106,7 +2106,7 @@ void HistoryInner::toggleFavoriteReaction(not_null view) const { item->toggleReaction(favorite, HistoryItem::ReactionSource::Quick); } -TextWithEntities HistoryInner::selectedQuote( +HistoryView::SelectedQuote HistoryInner::selectedQuote( not_null item) const { if (_selected.size() != 1 || _selected.begin()->first != item @@ -2393,11 +2393,13 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { }(); const auto canReply = canSendReply || item->allowsForward(); if (canReply) { - const auto itemId = item->fullId(); - const auto quote = selectedQuote(item); - auto text = quote.empty() - ? tr::lng_context_reply_msg(tr::now) - : tr::lng_context_quote_and_reply(tr::now); + const auto selected = selectedQuote(item); + auto text = selected + ? tr::lng_context_quote_and_reply(tr::now) + : tr::lng_context_reply_msg(tr::now); + const auto replyToItem = selected.item ? selected.item : item; + const auto itemId = replyToItem->fullId(); + const auto quote = selected.text; text.replace('&', u"&&"_q); _menu->addAction(text, [=] { if (canSendReply) { diff --git a/Telegram/SourceFiles/history/history_inner_widget.h b/Telegram/SourceFiles/history/history_inner_widget.h index c6049a844..810521f50 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.h +++ b/Telegram/SourceFiles/history/history_inner_widget.h @@ -35,6 +35,7 @@ class EmptyPainter; class Element; class TranslateTracker; struct PinnedId; +struct SelectedQuote; } // namespace HistoryView namespace HistoryView::Reactions { @@ -314,7 +315,7 @@ private: QPoint mapPointToItem(QPoint p, const Element *view) const; QPoint mapPointToItem(QPoint p, const HistoryItem *item) const; - [[nodiscard]] TextWithEntities selectedQuote( + [[nodiscard]] HistoryView::SelectedQuote selectedQuote( not_null item) const; void showContextMenu(QContextMenuEvent *e, bool showFromTouch = false); diff --git a/Telegram/SourceFiles/history/history_view_highlight_manager.cpp b/Telegram/SourceFiles/history/history_view_highlight_manager.cpp index 94244ef00..29e157344 100644 --- a/Telegram/SourceFiles/history/history_view_highlight_manager.cpp +++ b/Telegram/SourceFiles/history/history_view_highlight_manager.cpp @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history_view_highlight_manager.h" #include "data/data_session.h" +#include "history/history.h" #include "history/history_item.h" #include "history/view/history_view_element.h" #include "ui/chat/chat_style.h" @@ -26,17 +27,22 @@ ElementHighlighter::ElementHighlighter( void ElementHighlighter::enqueue( not_null view, - TextSelection part) { - const auto item = view->data(); - const auto data = Highlight{ item->fullId(), part }; + const TextWithEntities &part) { + const auto data = computeHighlight(view, part); if (_queue.empty() && !_animation.animating()) { - highlight(data.itemId, data.part); + highlight(data); } else if (_highlighted != data && !base::contains(_queue, data)) { _queue.push_back(data); checkNextHighlight(); } } +void ElementHighlighter::highlight( + not_null view, + const TextWithEntities &part) { + highlight(computeHighlight(view, part)); +} + void ElementHighlighter::checkNextHighlight() { if (_animation.animating()) { return; @@ -53,10 +59,9 @@ void ElementHighlighter::checkNextHighlight() { } return Highlight(); }(); - if (!next) { - return; + if (next) { + highlight(next); } - highlight(next.itemId, next.part); } Ui::ChatPaintHighlight ElementHighlighter::state( @@ -69,18 +74,46 @@ Ui::ChatPaintHighlight ElementHighlighter::state( return {}; } -void ElementHighlighter::highlight(FullMsgId itemId, TextSelection part) { - if (const auto item = _data->message(itemId)) { +ElementHighlighter::Highlight ElementHighlighter::computeHighlight( + not_null view, + const TextWithEntities &part) { + const auto item = view->data(); + const auto owner = &item->history()->owner(); + if (const auto group = owner->groups().find(item)) { + const auto leader = group->items.front(); + const auto leaderId = leader->fullId(); + const auto i = ranges::find(group->items, item); + if (i != end(group->items)) { + const auto index = int(i - begin(group->items)); + if (part.empty()) { + return { leaderId, AddGroupItemSelection({}, index) }; + } else if (const auto leaderView = _viewForItem(leader)) { + return { + leaderId, + leaderView->selectionFromQuote(item, part), + }; + } + } + return { leaderId }; + } else if (part.empty()) { + return { item->fullId() }; + } + return { item->fullId(), view->selectionFromQuote(item, part) }; +} + +void ElementHighlighter::highlight(Highlight data) { + if (const auto item = _data->message(data.itemId)) { if (const auto view = _viewForItem(item)) { - if (_highlighted && _highlighted.itemId != itemId) { + if (_highlighted && _highlighted.itemId != data.itemId) { if (const auto was = _data->message(_highlighted.itemId)) { if (const auto view = _viewForItem(was)) { repaintHighlightedItem(view); } } } - _highlighted = { itemId, part }; - _animation.start(!part.empty()); + _highlighted = data; + _animation.start(!data.part.empty() + && !IsSubGroupSelection(data.part)); repaintHighlightedItem(view); } diff --git a/Telegram/SourceFiles/history/history_view_highlight_manager.h b/Telegram/SourceFiles/history/history_view_highlight_manager.h index f6de65ceb..7d74ed9a5 100644 --- a/Telegram/SourceFiles/history/history_view_highlight_manager.h +++ b/Telegram/SourceFiles/history/history_view_highlight_manager.h @@ -33,8 +33,8 @@ public: ViewForItem viewForItem, RepaintView repaintView); - void enqueue(not_null view, TextSelection part); - void highlight(FullMsgId itemId, TextSelection part); + void enqueue(not_null view, const TextWithEntities &part); + void highlight(not_null view, const TextWithEntities &part); void clear(); [[nodiscard]] Ui::ChatPaintHighlight state( @@ -72,6 +72,10 @@ private: friend inline bool operator==(Highlight, Highlight) = default; }; + [[nodiscard]] Highlight computeHighlight( + not_null view, + const TextWithEntities &part); + void highlight(Highlight data); void checkNextHighlight(); void repaintHighlightedItem(not_null view); void updateMessage(); diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index b0e5f8d2a..de130e3c1 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -1270,7 +1270,7 @@ void HistoryWidget::scrollToAnimationCallback( void HistoryWidget::enqueueMessageHighlight( not_null view, - TextSelection part) { + const TextWithEntities &part) { _highlighter.enqueue(view, part); } @@ -5709,8 +5709,7 @@ int HistoryWidget::countInitialScrollTop() { enqueueMessageHighlight( view, - view->selectionFromQuote( - base::take(_showAtMsgHighlightPart))); + base::take(_showAtMsgHighlightPart)); const auto result = itemTopForHighlight(view); createUnreadBarIfBelowVisibleArea(result); return result; diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h index ebc1a04c8..4bf971ff2 100644 --- a/Telegram/SourceFiles/history/history_widget.h +++ b/Telegram/SourceFiles/history/history_widget.h @@ -184,7 +184,7 @@ public: void enqueueMessageHighlight( not_null view, - TextSelection part); + const TextWithEntities &part); [[nodiscard]] Ui::ChatPaintHighlight itemHighlight( not_null item) const; diff --git a/Telegram/SourceFiles/history/view/controls/history_view_draft_options.cpp b/Telegram/SourceFiles/history/view/controls/history_view_draft_options.cpp index 9b2b3f889..9a4b2ba53 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_draft_options.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_draft_options.cpp @@ -217,7 +217,9 @@ rpl::producer PreviewWrap::showQuoteSelector( const TextWithEntities "e) { _selection.reset(TextSelection()); - _element = item->createView(_delegate.get()); + const auto group = item->history()->owner().groups().find(item); + const auto leader = group ? group->items.front() : item; + _element = leader->createView(_delegate.get()); _link = _pressedLink = nullptr; if (const auto was = base::take(_draftItem)) { @@ -233,10 +235,10 @@ rpl::producer PreviewWrap::showQuoteSelector( initElement(); - _selection = _element->selectionFromQuote(quote); + _selection = _element->selectionFromQuote(item, quote); return _selection.value( ) | rpl::map([=](TextSelection selection) { - return _element->selectedQuote(selection); + return _element->selectedQuote(selection).text; }); } diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.h b/Telegram/SourceFiles/history/view/history_view_context_menu.h index 64040ae41..fb99d2cc6 100644 --- a/Telegram/SourceFiles/history/view/history_view_context_menu.h +++ b/Telegram/SourceFiles/history/view/history_view_context_menu.h @@ -48,6 +48,7 @@ struct ContextMenuRequest { SelectedItems selectedItems; TextForMimeData selectedText; TextWithEntities quote; + HistoryItem *quoteItem = nullptr; bool overSelection = false; PointState pointState = PointState(); }; diff --git a/Telegram/SourceFiles/history/view/history_view_element.cpp b/Telegram/SourceFiles/history/view/history_view_element.cpp index ac3cf2ad2..68f8c49ad 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.cpp +++ b/Telegram/SourceFiles/history/view/history_view_element.cpp @@ -94,6 +94,42 @@ Element *MousedElement/* = nullptr*/; return session->tryResolveWindow(); } +[[nodiscard]] bool CheckQuoteEntities( + const EntitiesInText "eEntities, + const TextWithEntities &original, + TextSelection selection) { + auto left = quoteEntities; + const auto allowed = std::array{ + EntityType::Bold, + EntityType::Italic, + EntityType::Underline, + EntityType::StrikeOut, + EntityType::Spoiler, + EntityType::CustomEmoji, + }; + for (const auto &entity : original.entities) { + const auto from = entity.offset(); + const auto till = from + entity.length(); + if (till <= selection.from || from >= selection.to) { + continue; + } + const auto quoteFrom = std::max(from, int(selection.from)); + const auto quoteTill = std::min(till, int(selection.to)); + const auto cut = EntityInText( + entity.type(), + quoteFrom - int(selection.from), + quoteTill - quoteFrom, + entity.data()); + const auto i = ranges::find(left, cut); + if (i != left.end()) { + left.erase(i); + } else if (ranges::contains(allowed, cut.type())) { + return false; + } + } + return left.empty(); +}; + } // namespace std::unique_ptr MakePathShiftGradient( @@ -1559,6 +1595,105 @@ TextSelection Element::adjustSelection( return selection; } +SelectedQuote Element::FindSelectedQuote( + const Ui::Text::String &text, + TextSelection selection, + not_null item) { + if (selection.to > text.length()) { + return {}; + } + auto modified = selection; + for (const auto &modification : text.modifications()) { + if (modification.position >= selection.to) { + break; + } else if (modification.position <= selection.from) { + modified.from += modification.skipped; + if (modification.added + && modification.position < selection.from) { + --modified.from; + } + } + modified.to += modification.skipped; + if (modification.added && modified.to > modified.from) { + --modified.to; + } + } + auto result = item->originalText(); + if (modified.empty() || modified.to > result.text.size()) { + return {}; + } + result.text = result.text.mid( + modified.from, + modified.to - modified.from); + const auto allowed = std::array{ + EntityType::Bold, + EntityType::Italic, + EntityType::Underline, + EntityType::StrikeOut, + EntityType::Spoiler, + EntityType::CustomEmoji, + }; + for (auto i = result.entities.begin(); i != result.entities.end();) { + const auto offset = i->offset(); + const auto till = offset + i->length(); + if ((till <= modified.from) + || (offset >= modified.to) + || !ranges::contains(allowed, i->type())) { + i = result.entities.erase(i); + } else { + if (till > modified.to) { + i->shrinkFromRight(till - modified.to); + } + i->shiftLeft(modified.from); + ++i; + } + } + return { item, result }; +} + +TextSelection Element::FindSelectionFromQuote( + const Ui::Text::String &text, + not_null item, + const TextWithEntities "e) { + if (quote.empty()) { + return {}; + } + const auto &original = item->originalText(); + auto result = TextSelection(); + auto offset = 0; + while (true) { + const auto i = original.text.indexOf(quote.text, offset); + if (i < 0) { + return {}; + } + auto selection = TextSelection{ + uint16(i), + uint16(i + quote.text.size()), + }; + if (CheckQuoteEntities(quote.entities, original, selection)) { + result = selection; + break; + } + offset = i + 1; + } + //for (const auto &modification : text.modifications()) { + // if (modification.position >= selection.to) { + // break; + // } else if (modification.position <= selection.from) { + // modified.from += modification.skipped; + // if (modification.added + // && modification.position < selection.from) { + // --modified.from; + // } + // } + // modified.to += modification.skipped; + // if (modification.added && modified.to > modified.from) { + // --modified.to; + // } + //} + return result; +} + Reactions::ButtonParameters Element::reactionButtonParameters( QPoint position, const TextState &reactionState) const { diff --git a/Telegram/SourceFiles/history/view/history_view_element.h b/Telegram/SourceFiles/history/view/history_view_element.h index fa8e9e4fa..8261628d7 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.h +++ b/Telegram/SourceFiles/history/view/history_view_element.h @@ -266,6 +266,15 @@ struct TopicButton { int nameVersion = 0; }; +struct SelectedQuote { + HistoryItem *item = nullptr; + TextWithEntities text; + + explicit operator bool() const { + return item && !text.empty(); + } +}; + class Element : public Object , public RuntimeComposer @@ -387,19 +396,24 @@ public: QPoint point, InfoDisplayType type) const; virtual TextForMimeData selectedText(TextSelection selection) const = 0; - virtual TextWithEntities selectedQuote(TextSelection selection) const = 0; - virtual TextWithEntities selectedQuote( - const Ui::Text::String &text, + virtual SelectedQuote selectedQuote( TextSelection selection) const = 0; virtual TextSelection selectionFromQuote( - const TextWithEntities "e) const = 0; - virtual TextSelection selectionFromQuote( - const Ui::Text::String &text, + not_null item, const TextWithEntities "e) const = 0; [[nodiscard]] virtual TextSelection adjustSelection( TextSelection selection, TextSelectType type) const; + [[nodiscard]] static SelectedQuote FindSelectedQuote( + const Ui::Text::String &text, + TextSelection selection, + not_null item); + [[nodiscard]] static TextSelection FindSelectionFromQuote( + const Ui::Text::String &text, + not_null item, + const TextWithEntities "e); + [[nodiscard]] virtual auto reactionButtonParameters( QPoint position, const TextState &reactionState) const -> Reactions::ButtonParameters; diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp index 07f63ce4f..28932369a 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp @@ -709,13 +709,10 @@ bool ListWidget::isBelowPosition(Data::MessagePosition position) const { void ListWidget::highlightMessage( FullMsgId itemId, - const TextWithEntities &highlightPart) { - const auto view = !highlightPart.empty() - ? viewForItem(itemId) - : nullptr; - _highlighter.highlight( - itemId, - view ? view->selectionFromQuote(highlightPart) : TextSelection()); + const TextWithEntities &part) { + if (const auto view = viewForItem(itemId)) { + _highlighter.highlight(view, part); + } } void ListWidget::showAroundPosition( @@ -2607,9 +2604,11 @@ void ListWidget::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { request.view = _overElement; request.item = overItem; request.pointState = _overState.pointState; - request.quote = (overItemView && _selectedTextItem == overItem) + const auto quote = (overItemView && _selectedTextItem == overItem) ? overItemView->selectedQuote(_selectedTextRange) - : TextWithEntities(); + : SelectedQuote(); + request.quote = quote.text; + request.quoteItem = quote.item; request.selectedText = _selectedText; request.selectedItems = collectSelectedItems(); const auto hasSelection = !request.selectedItems.empty() diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.h b/Telegram/SourceFiles/history/view/history_view_list_widget.h index fd56b9313..95b641a04 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.h +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.h @@ -233,7 +233,7 @@ public: bool isBelowPosition(Data::MessagePosition position) const; void highlightMessage( FullMsgId itemId, - const TextWithEntities &highlightPart); + const TextWithEntities &part); void showAtPosition( Data::MessagePosition position, diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index cf2d82973..a73b2e18c 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -65,42 +65,6 @@ const auto kPsaTooltipPrefix = "cloud_lng_tooltip_psa_"; return std::nullopt; } -[[nodiscard]] bool CheckQuoteEntities( - const EntitiesInText "eEntities, - const TextWithEntities &original, - TextSelection selection) { - auto left = quoteEntities; - const auto allowed = std::array{ - EntityType::Bold, - EntityType::Italic, - EntityType::Underline, - EntityType::StrikeOut, - EntityType::Spoiler, - EntityType::CustomEmoji, - }; - for (const auto &entity : original.entities) { - const auto from = entity.offset(); - const auto till = from + entity.length(); - if (till <= selection.from || from >= selection.to) { - continue; - } - const auto quoteFrom = std::max(from, int(selection.from)); - const auto quoteTill = std::min(till, int(selection.to)); - const auto cut = EntityInText( - entity.type(), - quoteFrom - int(selection.from), - quoteTill - quoteFrom, - entity.data()); - const auto i = ranges::find(left, cut); - if (i != left.end()) { - left.erase(i); - } else if (ranges::contains(allowed, cut.type())) { - return false; - } - } - return left.empty(); -}; - class KeyboardStyle : public ReplyKeyboard::Style { public: KeyboardStyle(const style::BotKeyboardButton &st); @@ -2682,7 +2646,7 @@ TextForMimeData Message::selectedText(TextSelection selection) const { return result; } -TextWithEntities Message::selectedQuote(TextSelection selection) const { +SelectedQuote Message::selectedQuote(TextSelection selection) const { const auto item = data(); const auto &translated = item->translatedText(); const auto &original = item->originalText(); @@ -2697,7 +2661,7 @@ TextWithEntities Message::selectedQuote(TextSelection selection) const { const auto textSelection = mediaBefore ? media->skipSelection(selection) : selection; - return selectedQuote(text(), textSelection); + return FindSelectedQuote(text(), textSelection, data()); } else if (const auto media = this->media()) { if (media->isDisplayed() || isHiddenByGroup()) { return media->selectedQuote(selection); @@ -2706,67 +2670,12 @@ TextWithEntities Message::selectedQuote(TextSelection selection) const { return {}; } -TextWithEntities Message::selectedQuote( - const Ui::Text::String &text, - TextSelection selection) const { - if (selection.to > text.length()) { - return {}; - } - auto modified = selection; - for (const auto &modification : text.modifications()) { - if (modification.position >= selection.to) { - break; - } else if (modification.position <= selection.from) { - modified.from += modification.skipped; - if (modification.added - && modification.position < selection.from) { - --modified.from; - } - } - modified.to += modification.skipped; - if (modification.added && modified.to > modified.from) { - --modified.to; - } - } - auto result = data()->originalText(); - if (modified.empty() || modified.to > result.text.size()) { - return {}; - } - result.text = result.text.mid( - modified.from, - modified.to - modified.from); - const auto allowed = std::array{ - EntityType::Bold, - EntityType::Italic, - EntityType::Underline, - EntityType::StrikeOut, - EntityType::Spoiler, - EntityType::CustomEmoji, - }; - for (auto i = result.entities.begin(); i != result.entities.end();) { - const auto offset = i->offset(); - const auto till = offset + i->length(); - if ((till <= modified.from) - || (offset >= modified.to) - || !ranges::contains(allowed, i->type())) { - i = result.entities.erase(i); - } else { - if (till > modified.to) { - i->shrinkFromRight(till - modified.to); - } - i->shiftLeft(modified.from); - ++i; - } - } - return result; -} - TextSelection Message::selectionFromQuote( + not_null item, const TextWithEntities "e) const { if (quote.empty()) { return {}; } - const auto item = data(); const auto &translated = item->translatedText(); const auto &original = item->originalText(); if (&translated != &original) { @@ -2775,58 +2684,16 @@ TextSelection Message::selectionFromQuote( const auto media = this->media(); const auto mediaDisplayed = media && media->isDisplayed(); const auto mediaBefore = mediaDisplayed && invertMedia(); - const auto result = selectionFromQuote(text(), quote); + const auto result = FindSelectionFromQuote(text(), item, quote); return mediaBefore ? media->unskipSelection(result) : result; } else if (const auto media = this->media()) { if (media->isDisplayed() || isHiddenByGroup()) { - return media->selectionFromQuote(quote); + return media->selectionFromQuote(item, quote); } } return {}; } -TextSelection Message::selectionFromQuote( - const Ui::Text::String &text, - const TextWithEntities "e) const { - if (quote.empty()) { - return {}; - } - const auto &original = data()->originalText(); - auto result = TextSelection(); - auto offset = 0; - while (true) { - const auto i = original.text.indexOf(quote.text, offset); - if (i < 0) { - return {}; - } - auto selection = TextSelection{ - uint16(i), - uint16(i + quote.text.size()), - }; - if (CheckQuoteEntities(quote.entities, original, selection)) { - result = selection; - break; - } - offset = i + 1; - } - //for (const auto &modification : text.modifications()) { - // if (modification.position >= selection.to) { - // break; - // } else if (modification.position <= selection.from) { - // modified.from += modification.skipped; - // if (modification.added - // && modification.position < selection.from) { - // --modified.from; - // } - // } - // modified.to += modification.skipped; - // if (modification.added && modified.to > modified.from) { - // --modified.to; - // } - //} - return result; -} - TextSelection Message::adjustSelection( TextSelection selection, TextSelectType type) const { diff --git a/Telegram/SourceFiles/history/view/history_view_message.h b/Telegram/SourceFiles/history/view/history_view_message.h index 7506bf355..aa66b0cf5 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.h +++ b/Telegram/SourceFiles/history/view/history_view_message.h @@ -95,14 +95,9 @@ public: QPoint point, InfoDisplayType type) const override; TextForMimeData selectedText(TextSelection selection) const override; - TextWithEntities selectedQuote(TextSelection selection) const override; - TextWithEntities selectedQuote( - const Ui::Text::String &text, - TextSelection selection) const override; + SelectedQuote selectedQuote(TextSelection selection) const override; TextSelection selectionFromQuote( - const TextWithEntities "e) const override; - TextSelection selectionFromQuote( - const Ui::Text::String &text, + not_null item, const TextWithEntities "e) const override; TextSelection adjustSelection( TextSelection selection, diff --git a/Telegram/SourceFiles/history/view/history_view_service_message.cpp b/Telegram/SourceFiles/history/view/history_view_service_message.cpp index a3d6550c5..0705ff2a5 100644 --- a/Telegram/SourceFiles/history/view/history_view_service_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_service_message.cpp @@ -669,23 +669,12 @@ TextForMimeData Service::selectedText(TextSelection selection) const { return text().toTextForMimeData(selection); } -TextWithEntities Service::selectedQuote(TextSelection selection) const { - return {}; -} - -TextWithEntities Service::selectedQuote( - const Ui::Text::String &text, - TextSelection selection) const { +SelectedQuote Service::selectedQuote(TextSelection selection) const { return {}; } TextSelection Service::selectionFromQuote( - const TextWithEntities "e) const { - return {}; -} - -TextSelection Service::selectionFromQuote( - const Ui::Text::String &text, + not_null item, const TextWithEntities "e) const { return {}; } diff --git a/Telegram/SourceFiles/history/view/history_view_service_message.h b/Telegram/SourceFiles/history/view/history_view_service_message.h index c862ce657..617dd1adb 100644 --- a/Telegram/SourceFiles/history/view/history_view_service_message.h +++ b/Telegram/SourceFiles/history/view/history_view_service_message.h @@ -43,14 +43,9 @@ public: StateRequest request) const override; void updatePressed(QPoint point) override; TextForMimeData selectedText(TextSelection selection) const override; - TextWithEntities selectedQuote(TextSelection selection) const override; - TextWithEntities selectedQuote( - const Ui::Text::String &text, - TextSelection selection) const override; + SelectedQuote selectedQuote(TextSelection selection) const override; TextSelection selectionFromQuote( - const TextWithEntities "e) const override; - TextSelection selectionFromQuote( - const Ui::Text::String &text, + not_null item, const TextWithEntities "e) const override; TextSelection adjustSelection( TextSelection selection, diff --git a/Telegram/SourceFiles/history/view/media/history_view_document.cpp b/Telegram/SourceFiles/history/view/media/history_view_document.cpp index 7346966da..7530b31ff 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_document.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_document.cpp @@ -1212,7 +1212,7 @@ TextForMimeData Document::selectedText(TextSelection selection) const { return result; } -TextWithEntities Document::selectedQuote(TextSelection selection) const { +SelectedQuote Document::selectedQuote(TextSelection selection) const { if (const auto voice = Get()) { const auto length = voice->transcribeText.length(); if (selection.from < length) { @@ -1223,16 +1223,21 @@ TextWithEntities Document::selectedQuote(TextSelection selection) const { voice->transcribeText); } if (const auto captioned = Get()) { - return parent()->selectedQuote(captioned->caption, selection); + return Element::FindSelectedQuote( + captioned->caption, + selection, + _realParent); } return {}; } TextSelection Document::selectionFromQuote( + not_null item, const TextWithEntities "e) const { if (const auto captioned = Get()) { - const auto result = parent()->selectionFromQuote( + const auto result = Element::FindSelectionFromQuote( captioned->caption, + item, quote); if (result.empty()) { return {}; diff --git a/Telegram/SourceFiles/history/view/media/history_view_document.h b/Telegram/SourceFiles/history/view/media/history_view_document.h index 31dd9886a..ec2dc0d78 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_document.h +++ b/Telegram/SourceFiles/history/view/media/history_view_document.h @@ -46,8 +46,9 @@ public: bool hasTextForCopy() const override; TextForMimeData selectedText(TextSelection selection) const override; - TextWithEntities selectedQuote(TextSelection selection) const override; + SelectedQuote selectedQuote(TextSelection selection) const override; TextSelection selectionFromQuote( + not_null item, const TextWithEntities "e) const override; bool uploading() const override; diff --git a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp index d35658d88..340b1fa66 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp @@ -1205,13 +1205,14 @@ TextForMimeData Gif::selectedText(TextSelection selection) const { return _caption.toTextForMimeData(selection); } -TextWithEntities Gif::selectedQuote(TextSelection selection) const { - return parent()->selectedQuote(_caption, selection); +SelectedQuote Gif::selectedQuote(TextSelection selection) const { + return Element::FindSelectedQuote(_caption, selection, _realParent); } TextSelection Gif::selectionFromQuote( + not_null item, const TextWithEntities "e) const { - return parent()->selectionFromQuote(_caption, quote); + return Element::FindSelectionFromQuote(_caption, item, quote); } bool Gif::fullFeaturedGrouped(RectParts sides) const { diff --git a/Telegram/SourceFiles/history/view/media/history_view_gif.h b/Telegram/SourceFiles/history/view/media/history_view_gif.h index fdba9a0af..eac33cc7a 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_gif.h +++ b/Telegram/SourceFiles/history/view/media/history_view_gif.h @@ -68,8 +68,9 @@ public: } TextForMimeData selectedText(TextSelection selection) const override; - TextWithEntities selectedQuote(TextSelection selection) const override; + SelectedQuote selectedQuote(TextSelection selection) const override; TextSelection selectionFromQuote( + not_null item, const TextWithEntities "e) const override; bool uploading() const override; diff --git a/Telegram/SourceFiles/history/view/media/history_view_media.cpp b/Telegram/SourceFiles/history/view/media/history_view_media.cpp index 4e19fa291..69cad0ec4 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media.cpp @@ -189,6 +189,10 @@ not_null Media::history() const { return _parent->history(); } +SelectedQuote Media::selectedQuote(TextSelection selection) const { + return {}; +} + bool Media::isDisplayed() const { return true; } diff --git a/Telegram/SourceFiles/history/view/media/history_view_media.h b/Telegram/SourceFiles/history/view/media/history_view_media.h index bda57224e..392806575 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media.h +++ b/Telegram/SourceFiles/history/view/media/history_view_media.h @@ -49,6 +49,7 @@ struct StateRequest; struct MediaSpoiler; class StickerPlayer; class Element; +struct SelectedQuote; using PaintContext = Ui::ChatPaintContext; @@ -88,11 +89,10 @@ public: TextSelection selection) const { return {}; } - [[nodiscard]] virtual TextWithEntities selectedQuote( - TextSelection selection) const { - return {}; - } + [[nodiscard]] virtual SelectedQuote selectedQuote( + TextSelection selection) const; [[nodiscard]] virtual TextSelection selectionFromQuote( + not_null item, const TextWithEntities "e) const { return {}; } diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp index 37676611d..9766c5aaa 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp @@ -286,16 +286,44 @@ void GroupedMedia::drawHighlight( Painter &p, const PaintContext &context, int top) const { + auto selection = context.highlight.range; if (_mode != Mode::Column) { + if (!selection.empty() && !IsSubGroupSelection(selection)) { + _parent->paintCustomHighlight( + p, + context, + top, + height(), + _parent->data().get()); + } return; } + const auto empty = selection.empty(); + const auto subpart = IsSubGroupSelection(selection); const auto skip = top + groupedPadding().top(); for (auto i = 0, count = int(_parts.size()); i != count; ++i) { const auto &part = _parts[i]; const auto rect = part.geometry.translated(0, skip); + const auto full = (!i && empty) + || (subpart && IsGroupItemSelection(selection, i)); + auto copy = context; + if (full) { + copy.highlight.range = {}; + _parent->paintCustomHighlight( + p, + copy, + rect.y(), + rect.height(), + part.item); + } else if (!selection.empty()) { + copy.highlight.range = selection; + selection = part.content->skipSelection(selection); + } else { + break; + } _parent->paintCustomHighlight( p, - context, + copy, rect.y(), rect.height(), part.item); @@ -316,6 +344,7 @@ void GroupedMedia::draw(Painter &p, const PaintContext &context) const { const auto rounding = inWebPage ? Ui::BubbleRounding{ kSmall, kSmall, kSmall, kSmall } : adjustedBubbleRoundingWithCaption(_caption); + const auto highlight = context.highlight.range; for (auto i = 0, count = int(_parts.size()); i != count; ++i) { const auto &part = _parts[i]; const auto partContext = context.withSelection(fullSelection @@ -325,10 +354,11 @@ void GroupedMedia::draw(Painter &p, const PaintContext &context) const { : IsGroupItemSelection(selection, i) ? FullSelection : TextSelection()); - const auto highlightOpacity = IsGroupItemSelection( - context.highlight.range, - i - ) ? context.highlight.opacity : 0.; + const auto highlighted = (highlight.empty() && !i) + || IsGroupItemSelection(highlight, i); + const auto highlightOpacity = highlighted + ? context.highlight.opacity + : 0.; if (textSelection) { selection = part.content->skipSelection(selection); } @@ -517,6 +547,7 @@ TextSelection GroupedMedia::adjustSelection( selection.to = modified.to; return selection; } + checked = till; } return selection; } @@ -564,6 +595,50 @@ TextForMimeData GroupedMedia::selectedText( return result; } +SelectedQuote GroupedMedia::selectedQuote(TextSelection selection) const { + if (_mode != Mode::Column) { + return _captionItem + ? Element::FindSelectedQuote(_caption, selection, _captionItem) + : SelectedQuote(); + } + for (const auto &part : _parts) { + const auto next = part.content->skipSelection(selection); + if (next.to - next.from != selection.to - selection.from) { + if (!next.empty()) { + return SelectedQuote(); + } + auto result = part.content->selectedQuote(selection); + result.item = part.item; + return result; + } + selection = next; + } + return {}; +} + +TextSelection GroupedMedia::selectionFromQuote( + not_null item, + const TextWithEntities "e) const { + if (_mode != Mode::Column) { + return (_captionItem == item) + ? Element::FindSelectionFromQuote(_caption, item, quote) + : TextSelection(); + } + const auto i = ranges::find(_parts, item, &Part::item); + if (i == end(_parts)) { + return {}; + } + const auto index = int(i - begin(_parts)); + auto result = i->content->selectionFromQuote(item, quote); + if (result.empty()) { + return AddGroupItemSelection({}, index); + } + for (auto j = i; j != begin(_parts);) { + result = (--j)->content->unskipSelection(result); + } + return result; +} + auto GroupedMedia::getBubbleSelectionIntervals( TextSelection selection) const -> std::vector { @@ -666,16 +741,15 @@ bool GroupedMedia::validateGroupParts( } void GroupedMedia::refreshCaption() { - using PartPtrOpt = std::optional; - const auto captionPart = [&]() -> PartPtrOpt { + const auto part = [&]() -> const Part* { if (_mode == Mode::Column) { - return std::nullopt; + return nullptr; } - auto result = PartPtrOpt(); + auto result = (const Part*)nullptr; for (const auto &part : _parts) { if (!part.item->emptyText()) { if (result) { - return std::nullopt; + return nullptr; } else { result = ∂ } @@ -683,8 +757,7 @@ void GroupedMedia::refreshCaption() { } return result; }(); - if (captionPart) { - const auto &part = (*captionPart); + if (part) { _caption = createCaption(part->item); _captionItem = part->item; } else { diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.h b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.h index c66864063..b6fbc77d9 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.h +++ b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.h @@ -55,6 +55,10 @@ public: DocumentData *getDocument() const override; TextForMimeData selectedText(TextSelection selection) const override; + SelectedQuote selectedQuote(TextSelection selection) const override; + TextSelection selectionFromQuote( + not_null item, + const TextWithEntities "e) const override; std::vector getBubbleSelectionIntervals( TextSelection selection) const override; diff --git a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp index b0afe1b64..de72e7786 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp @@ -1051,13 +1051,14 @@ TextForMimeData Photo::selectedText(TextSelection selection) const { return _caption.toTextForMimeData(selection); } -TextWithEntities Photo::selectedQuote(TextSelection selection) const { - return parent()->selectedQuote(_caption, selection); +SelectedQuote Photo::selectedQuote(TextSelection selection) const { + return Element::FindSelectedQuote(_caption, selection, _realParent); } TextSelection Photo::selectionFromQuote( + not_null item, const TextWithEntities "e) const { - return parent()->selectionFromQuote(_caption, quote); + return Element::FindSelectionFromQuote(_caption, item, quote); } void Photo::hideSpoilers() { diff --git a/Telegram/SourceFiles/history/view/media/history_view_photo.h b/Telegram/SourceFiles/history/view/media/history_view_photo.h index b76bbe264..7213dca21 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_photo.h +++ b/Telegram/SourceFiles/history/view/media/history_view_photo.h @@ -57,8 +57,9 @@ public: } TextForMimeData selectedText(TextSelection selection) const override; - TextWithEntities selectedQuote(TextSelection selection) const override; + SelectedQuote selectedQuote(TextSelection selection) const override; TextSelection selectionFromQuote( + not_null item, const TextWithEntities "e) const override; PhotoData *getPhoto() const override {