Highlight quotes in replies to albums.

This commit is contained in:
John Preston 2023-10-31 22:37:59 +04:00
parent 6493cb9ed8
commit 0dbb195106
26 changed files with 364 additions and 237 deletions

View File

@ -2106,7 +2106,7 @@ void HistoryInner::toggleFavoriteReaction(not_null<Element*> view) const {
item->toggleReaction(favorite, HistoryItem::ReactionSource::Quick);
}
TextWithEntities HistoryInner::selectedQuote(
HistoryView::SelectedQuote HistoryInner::selectedQuote(
not_null<HistoryItem*> 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) {

View File

@ -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<HistoryItem*> item) const;
void showContextMenu(QContextMenuEvent *e, bool showFromTouch = false);

View File

@ -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<Element*> 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<Element*> 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<const Element*> 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);
}

View File

@ -33,8 +33,8 @@ public:
ViewForItem viewForItem,
RepaintView repaintView);
void enqueue(not_null<Element*> view, TextSelection part);
void highlight(FullMsgId itemId, TextSelection part);
void enqueue(not_null<Element*> view, const TextWithEntities &part);
void highlight(not_null<Element*> 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<const Element*> view,
const TextWithEntities &part);
void highlight(Highlight data);
void checkNextHighlight();
void repaintHighlightedItem(not_null<const Element*> view);
void updateMessage();

View File

@ -1270,7 +1270,7 @@ void HistoryWidget::scrollToAnimationCallback(
void HistoryWidget::enqueueMessageHighlight(
not_null<HistoryView::Element*> 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;

View File

@ -184,7 +184,7 @@ public:
void enqueueMessageHighlight(
not_null<HistoryView::Element*> view,
TextSelection part);
const TextWithEntities &part);
[[nodiscard]] Ui::ChatPaintHighlight itemHighlight(
not_null<const HistoryItem*> item) const;

View File

@ -217,7 +217,9 @@ rpl::producer<TextWithEntities> PreviewWrap::showQuoteSelector(
const TextWithEntities &quote) {
_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<TextWithEntities> 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;
});
}

View File

@ -48,6 +48,7 @@ struct ContextMenuRequest {
SelectedItems selectedItems;
TextForMimeData selectedText;
TextWithEntities quote;
HistoryItem *quoteItem = nullptr;
bool overSelection = false;
PointState pointState = PointState();
};

View File

@ -94,6 +94,42 @@ Element *MousedElement/* = nullptr*/;
return session->tryResolveWindow();
}
[[nodiscard]] bool CheckQuoteEntities(
const EntitiesInText &quoteEntities,
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<Ui::PathShiftGradient> MakePathShiftGradient(
@ -1559,6 +1595,105 @@ TextSelection Element::adjustSelection(
return selection;
}
SelectedQuote Element::FindSelectedQuote(
const Ui::Text::String &text,
TextSelection selection,
not_null<HistoryItem*> 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<HistoryItem*> item,
const TextWithEntities &quote) {
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 {

View File

@ -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<Element>
@ -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 &quote) const = 0;
virtual TextSelection selectionFromQuote(
const Ui::Text::String &text,
not_null<HistoryItem*> item,
const TextWithEntities &quote) const = 0;
[[nodiscard]] virtual TextSelection adjustSelection(
TextSelection selection,
TextSelectType type) const;
[[nodiscard]] static SelectedQuote FindSelectedQuote(
const Ui::Text::String &text,
TextSelection selection,
not_null<HistoryItem*> item);
[[nodiscard]] static TextSelection FindSelectionFromQuote(
const Ui::Text::String &text,
not_null<HistoryItem*> item,
const TextWithEntities &quote);
[[nodiscard]] virtual auto reactionButtonParameters(
QPoint position,
const TextState &reactionState) const -> Reactions::ButtonParameters;

View File

@ -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()

View File

@ -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,

View File

@ -65,42 +65,6 @@ const auto kPsaTooltipPrefix = "cloud_lng_tooltip_psa_";
return std::nullopt;
}
[[nodiscard]] bool CheckQuoteEntities(
const EntitiesInText &quoteEntities,
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<HistoryItem*> item,
const TextWithEntities &quote) 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 &quote) 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 {

View File

@ -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 &quote) const override;
TextSelection selectionFromQuote(
const Ui::Text::String &text,
not_null<HistoryItem*> item,
const TextWithEntities &quote) const override;
TextSelection adjustSelection(
TextSelection selection,

View File

@ -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 &quote) const {
return {};
}
TextSelection Service::selectionFromQuote(
const Ui::Text::String &text,
not_null<HistoryItem*> item,
const TextWithEntities &quote) const {
return {};
}

View File

@ -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 &quote) const override;
TextSelection selectionFromQuote(
const Ui::Text::String &text,
not_null<HistoryItem*> item,
const TextWithEntities &quote) const override;
TextSelection adjustSelection(
TextSelection selection,

View File

@ -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<HistoryDocumentVoice>()) {
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<HistoryDocumentCaptioned>()) {
return parent()->selectedQuote(captioned->caption, selection);
return Element::FindSelectedQuote(
captioned->caption,
selection,
_realParent);
}
return {};
}
TextSelection Document::selectionFromQuote(
not_null<HistoryItem*> item,
const TextWithEntities &quote) const {
if (const auto captioned = Get<HistoryDocumentCaptioned>()) {
const auto result = parent()->selectionFromQuote(
const auto result = Element::FindSelectionFromQuote(
captioned->caption,
item,
quote);
if (result.empty()) {
return {};

View File

@ -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<HistoryItem*> item,
const TextWithEntities &quote) const override;
bool uploading() const override;

View File

@ -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<HistoryItem*> item,
const TextWithEntities &quote) const {
return parent()->selectionFromQuote(_caption, quote);
return Element::FindSelectionFromQuote(_caption, item, quote);
}
bool Gif::fullFeaturedGrouped(RectParts sides) const {

View File

@ -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<HistoryItem*> item,
const TextWithEntities &quote) const override;
bool uploading() const override;

View File

@ -189,6 +189,10 @@ not_null<History*> Media::history() const {
return _parent->history();
}
SelectedQuote Media::selectedQuote(TextSelection selection) const {
return {};
}
bool Media::isDisplayed() const {
return true;
}

View File

@ -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<HistoryItem*> item,
const TextWithEntities &quote) const {
return {};
}

View File

@ -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<HistoryItem*> item,
const TextWithEntities &quote) 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<Ui::BubbleSelectionInterval> {
@ -666,16 +741,15 @@ bool GroupedMedia::validateGroupParts(
}
void GroupedMedia::refreshCaption() {
using PartPtrOpt = std::optional<const Part*>;
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 = &part;
}
@ -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 {

View File

@ -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<HistoryItem*> item,
const TextWithEntities &quote) const override;
std::vector<Ui::BubbleSelectionInterval> getBubbleSelectionIntervals(
TextSelection selection) const override;

View File

@ -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<HistoryItem*> item,
const TextWithEntities &quote) const {
return parent()->selectionFromQuote(_caption, quote);
return Element::FindSelectionFromQuote(_caption, item, quote);
}
void Photo::hideSpoilers() {

View File

@ -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<HistoryItem*> item,
const TextWithEntities &quote) const override;
PhotoData *getPhoto() const override {