mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-16 06:07:06 +02:00
Highlight quotes in replies to albums.
This commit is contained in:
parent
6493cb9ed8
commit
0dbb195106
26 changed files with 364 additions and 237 deletions
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -217,7 +217,9 @@ rpl::producer<TextWithEntities> 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<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;
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -48,6 +48,7 @@ struct ContextMenuRequest {
|
|||
SelectedItems selectedItems;
|
||||
TextForMimeData selectedText;
|
||||
TextWithEntities quote;
|
||||
HistoryItem *quoteItem = nullptr;
|
||||
bool overSelection = false;
|
||||
PointState pointState = PointState();
|
||||
};
|
||||
|
|
|
@ -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<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 "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 {
|
||||
|
|
|
@ -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 "e) const = 0;
|
||||
virtual TextSelection selectionFromQuote(
|
||||
const Ui::Text::String &text,
|
||||
not_null<HistoryItem*> 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<HistoryItem*> item);
|
||||
[[nodiscard]] static TextSelection FindSelectionFromQuote(
|
||||
const Ui::Text::String &text,
|
||||
not_null<HistoryItem*> item,
|
||||
const TextWithEntities "e);
|
||||
|
||||
[[nodiscard]] virtual auto reactionButtonParameters(
|
||||
QPoint position,
|
||||
const TextState &reactionState) const -> Reactions::ButtonParameters;
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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<HistoryItem*> 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 {
|
||||
|
|
|
@ -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<HistoryItem*> item,
|
||||
const TextWithEntities "e) const override;
|
||||
TextSelection adjustSelection(
|
||||
TextSelection selection,
|
||||
|
|
|
@ -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<HistoryItem*> item,
|
||||
const TextWithEntities "e) const {
|
||||
return {};
|
||||
}
|
||||
|
|
|
@ -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<HistoryItem*> item,
|
||||
const TextWithEntities "e) const override;
|
||||
TextSelection adjustSelection(
|
||||
TextSelection selection,
|
||||
|
|
|
@ -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 "e) 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 {};
|
||||
|
|
|
@ -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 "e) const override;
|
||||
|
||||
bool uploading() const override;
|
||||
|
|
|
@ -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 "e) const {
|
||||
return parent()->selectionFromQuote(_caption, quote);
|
||||
return Element::FindSelectionFromQuote(_caption, item, quote);
|
||||
}
|
||||
|
||||
bool Gif::fullFeaturedGrouped(RectParts sides) const {
|
||||
|
|
|
@ -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 "e) const override;
|
||||
|
||||
bool uploading() const override;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 "e) const {
|
||||
return {};
|
||||
}
|
||||
|
|
|
@ -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 "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<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 = ∂
|
||||
}
|
||||
|
@ -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 {
|
||||
|
|
|
@ -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 "e) const override;
|
||||
|
||||
std::vector<Ui::BubbleSelectionInterval> getBubbleSelectionIntervals(
|
||||
TextSelection selection) const override;
|
||||
|
|
|
@ -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 "e) const {
|
||||
return parent()->selectionFromQuote(_caption, quote);
|
||||
return Element::FindSelectionFromQuote(_caption, item, quote);
|
||||
}
|
||||
|
||||
void Photo::hideSpoilers() {
|
||||
|
|
|
@ -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 "e) const override;
|
||||
|
||||
PhotoData *getPhoto() const override {
|
||||
|
|
Loading…
Add table
Reference in a new issue