Skip to content

Commit

Permalink
Highlight quotes in replies to albums.
Browse files Browse the repository at this point in the history
  • Loading branch information
john-preston committed Oct 31, 2023
1 parent 6493cb9 commit 0dbb195
Show file tree
Hide file tree
Showing 26 changed files with 364 additions and 237 deletions.
14 changes: 8 additions & 6 deletions Telegram/SourceFiles/history/history_inner_widget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand Down
3 changes: 2 additions & 1 deletion Telegram/SourceFiles/history/history_inner_widget.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ class EmptyPainter;
class Element;
class TranslateTracker;
struct PinnedId;
struct SelectedQuote;
} // namespace HistoryView

namespace HistoryView::Reactions {
Expand Down Expand Up @@ -314,7 +315,7 @@ class HistoryInner

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);
Expand Down
57 changes: 45 additions & 12 deletions Telegram/SourceFiles/history/history_view_highlight_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ For license and copyright information please follow this link:
#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"
Expand All @@ -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;
Expand All @@ -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(
Expand All @@ -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);
}
Expand Down
8 changes: 6 additions & 2 deletions Telegram/SourceFiles/history/history_view_highlight_manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ class ElementHighlighter final {
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(
Expand Down Expand Up @@ -72,6 +72,10 @@ class ElementHighlighter final {
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();
Expand Down
5 changes: 2 additions & 3 deletions Telegram/SourceFiles/history/history_widget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1270,7 +1270,7 @@ void HistoryWidget::scrollToAnimationCallback(

void HistoryWidget::enqueueMessageHighlight(
not_null<HistoryView::Element*> view,
TextSelection part) {
const TextWithEntities &part) {
_highlighter.enqueue(view, part);
}

Expand Down Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion Telegram/SourceFiles/history/history_widget.h
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ class HistoryWidget final

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand All @@ -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;
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ struct ContextMenuRequest {
SelectedItems selectedItems;
TextForMimeData selectedText;
TextWithEntities quote;
HistoryItem *quoteItem = nullptr;
bool overSelection = false;
PointState pointState = PointState();
};
Expand Down
135 changes: 135 additions & 0 deletions Telegram/SourceFiles/history/view/history_view_element.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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 {
Expand Down
Loading

0 comments on commit 0dbb195

Please sign in to comment.