Highlight quotes in replies to albums.
This commit is contained in:
parent
6493cb9ed8
commit
0dbb195106
|
@ -2106,7 +2106,7 @@ void HistoryInner::toggleFavoriteReaction(not_null<Element*> view) const {
|
||||||
item->toggleReaction(favorite, HistoryItem::ReactionSource::Quick);
|
item->toggleReaction(favorite, HistoryItem::ReactionSource::Quick);
|
||||||
}
|
}
|
||||||
|
|
||||||
TextWithEntities HistoryInner::selectedQuote(
|
HistoryView::SelectedQuote HistoryInner::selectedQuote(
|
||||||
not_null<HistoryItem*> item) const {
|
not_null<HistoryItem*> item) const {
|
||||||
if (_selected.size() != 1
|
if (_selected.size() != 1
|
||||||
|| _selected.begin()->first != item
|
|| _selected.begin()->first != item
|
||||||
|
@ -2393,11 +2393,13 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
|
||||||
}();
|
}();
|
||||||
const auto canReply = canSendReply || item->allowsForward();
|
const auto canReply = canSendReply || item->allowsForward();
|
||||||
if (canReply) {
|
if (canReply) {
|
||||||
const auto itemId = item->fullId();
|
const auto selected = selectedQuote(item);
|
||||||
const auto quote = selectedQuote(item);
|
auto text = selected
|
||||||
auto text = quote.empty()
|
? tr::lng_context_quote_and_reply(tr::now)
|
||||||
? tr::lng_context_reply_msg(tr::now)
|
: tr::lng_context_reply_msg(tr::now);
|
||||||
: tr::lng_context_quote_and_reply(tr::now);
|
const auto replyToItem = selected.item ? selected.item : item;
|
||||||
|
const auto itemId = replyToItem->fullId();
|
||||||
|
const auto quote = selected.text;
|
||||||
text.replace('&', u"&&"_q);
|
text.replace('&', u"&&"_q);
|
||||||
_menu->addAction(text, [=] {
|
_menu->addAction(text, [=] {
|
||||||
if (canSendReply) {
|
if (canSendReply) {
|
||||||
|
|
|
@ -35,6 +35,7 @@ class EmptyPainter;
|
||||||
class Element;
|
class Element;
|
||||||
class TranslateTracker;
|
class TranslateTracker;
|
||||||
struct PinnedId;
|
struct PinnedId;
|
||||||
|
struct SelectedQuote;
|
||||||
} // namespace HistoryView
|
} // namespace HistoryView
|
||||||
|
|
||||||
namespace HistoryView::Reactions {
|
namespace HistoryView::Reactions {
|
||||||
|
@ -314,7 +315,7 @@ private:
|
||||||
|
|
||||||
QPoint mapPointToItem(QPoint p, const Element *view) const;
|
QPoint mapPointToItem(QPoint p, const Element *view) const;
|
||||||
QPoint mapPointToItem(QPoint p, const HistoryItem *item) const;
|
QPoint mapPointToItem(QPoint p, const HistoryItem *item) const;
|
||||||
[[nodiscard]] TextWithEntities selectedQuote(
|
[[nodiscard]] HistoryView::SelectedQuote selectedQuote(
|
||||||
not_null<HistoryItem*> item) const;
|
not_null<HistoryItem*> item) const;
|
||||||
|
|
||||||
void showContextMenu(QContextMenuEvent *e, bool showFromTouch = false);
|
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 "history/history_view_highlight_manager.h"
|
||||||
|
|
||||||
#include "data/data_session.h"
|
#include "data/data_session.h"
|
||||||
|
#include "history/history.h"
|
||||||
#include "history/history_item.h"
|
#include "history/history_item.h"
|
||||||
#include "history/view/history_view_element.h"
|
#include "history/view/history_view_element.h"
|
||||||
#include "ui/chat/chat_style.h"
|
#include "ui/chat/chat_style.h"
|
||||||
|
@ -26,17 +27,22 @@ ElementHighlighter::ElementHighlighter(
|
||||||
|
|
||||||
void ElementHighlighter::enqueue(
|
void ElementHighlighter::enqueue(
|
||||||
not_null<Element*> view,
|
not_null<Element*> view,
|
||||||
TextSelection part) {
|
const TextWithEntities &part) {
|
||||||
const auto item = view->data();
|
const auto data = computeHighlight(view, part);
|
||||||
const auto data = Highlight{ item->fullId(), part };
|
|
||||||
if (_queue.empty() && !_animation.animating()) {
|
if (_queue.empty() && !_animation.animating()) {
|
||||||
highlight(data.itemId, data.part);
|
highlight(data);
|
||||||
} else if (_highlighted != data && !base::contains(_queue, data)) {
|
} else if (_highlighted != data && !base::contains(_queue, data)) {
|
||||||
_queue.push_back(data);
|
_queue.push_back(data);
|
||||||
checkNextHighlight();
|
checkNextHighlight();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ElementHighlighter::highlight(
|
||||||
|
not_null<Element*> view,
|
||||||
|
const TextWithEntities &part) {
|
||||||
|
highlight(computeHighlight(view, part));
|
||||||
|
}
|
||||||
|
|
||||||
void ElementHighlighter::checkNextHighlight() {
|
void ElementHighlighter::checkNextHighlight() {
|
||||||
if (_animation.animating()) {
|
if (_animation.animating()) {
|
||||||
return;
|
return;
|
||||||
|
@ -53,10 +59,9 @@ void ElementHighlighter::checkNextHighlight() {
|
||||||
}
|
}
|
||||||
return Highlight();
|
return Highlight();
|
||||||
}();
|
}();
|
||||||
if (!next) {
|
if (next) {
|
||||||
return;
|
highlight(next);
|
||||||
}
|
}
|
||||||
highlight(next.itemId, next.part);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ui::ChatPaintHighlight ElementHighlighter::state(
|
Ui::ChatPaintHighlight ElementHighlighter::state(
|
||||||
|
@ -69,18 +74,46 @@ Ui::ChatPaintHighlight ElementHighlighter::state(
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
void ElementHighlighter::highlight(FullMsgId itemId, TextSelection part) {
|
ElementHighlighter::Highlight ElementHighlighter::computeHighlight(
|
||||||
if (const auto item = _data->message(itemId)) {
|
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 (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 was = _data->message(_highlighted.itemId)) {
|
||||||
if (const auto view = _viewForItem(was)) {
|
if (const auto view = _viewForItem(was)) {
|
||||||
repaintHighlightedItem(view);
|
repaintHighlightedItem(view);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_highlighted = { itemId, part };
|
_highlighted = data;
|
||||||
_animation.start(!part.empty());
|
_animation.start(!data.part.empty()
|
||||||
|
&& !IsSubGroupSelection(data.part));
|
||||||
|
|
||||||
repaintHighlightedItem(view);
|
repaintHighlightedItem(view);
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,8 +33,8 @@ public:
|
||||||
ViewForItem viewForItem,
|
ViewForItem viewForItem,
|
||||||
RepaintView repaintView);
|
RepaintView repaintView);
|
||||||
|
|
||||||
void enqueue(not_null<Element*> view, TextSelection part);
|
void enqueue(not_null<Element*> view, const TextWithEntities &part);
|
||||||
void highlight(FullMsgId itemId, TextSelection part);
|
void highlight(not_null<Element*> view, const TextWithEntities &part);
|
||||||
void clear();
|
void clear();
|
||||||
|
|
||||||
[[nodiscard]] Ui::ChatPaintHighlight state(
|
[[nodiscard]] Ui::ChatPaintHighlight state(
|
||||||
|
@ -72,6 +72,10 @@ private:
|
||||||
friend inline bool operator==(Highlight, Highlight) = default;
|
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 checkNextHighlight();
|
||||||
void repaintHighlightedItem(not_null<const Element*> view);
|
void repaintHighlightedItem(not_null<const Element*> view);
|
||||||
void updateMessage();
|
void updateMessage();
|
||||||
|
|
|
@ -1270,7 +1270,7 @@ void HistoryWidget::scrollToAnimationCallback(
|
||||||
|
|
||||||
void HistoryWidget::enqueueMessageHighlight(
|
void HistoryWidget::enqueueMessageHighlight(
|
||||||
not_null<HistoryView::Element*> view,
|
not_null<HistoryView::Element*> view,
|
||||||
TextSelection part) {
|
const TextWithEntities &part) {
|
||||||
_highlighter.enqueue(view, part);
|
_highlighter.enqueue(view, part);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5709,8 +5709,7 @@ int HistoryWidget::countInitialScrollTop() {
|
||||||
|
|
||||||
enqueueMessageHighlight(
|
enqueueMessageHighlight(
|
||||||
view,
|
view,
|
||||||
view->selectionFromQuote(
|
base::take(_showAtMsgHighlightPart));
|
||||||
base::take(_showAtMsgHighlightPart)));
|
|
||||||
const auto result = itemTopForHighlight(view);
|
const auto result = itemTopForHighlight(view);
|
||||||
createUnreadBarIfBelowVisibleArea(result);
|
createUnreadBarIfBelowVisibleArea(result);
|
||||||
return result;
|
return result;
|
||||||
|
|
|
@ -184,7 +184,7 @@ public:
|
||||||
|
|
||||||
void enqueueMessageHighlight(
|
void enqueueMessageHighlight(
|
||||||
not_null<HistoryView::Element*> view,
|
not_null<HistoryView::Element*> view,
|
||||||
TextSelection part);
|
const TextWithEntities &part);
|
||||||
[[nodiscard]] Ui::ChatPaintHighlight itemHighlight(
|
[[nodiscard]] Ui::ChatPaintHighlight itemHighlight(
|
||||||
not_null<const HistoryItem*> item) const;
|
not_null<const HistoryItem*> item) const;
|
||||||
|
|
||||||
|
|
|
@ -217,7 +217,9 @@ rpl::producer<TextWithEntities> PreviewWrap::showQuoteSelector(
|
||||||
const TextWithEntities "e) {
|
const TextWithEntities "e) {
|
||||||
_selection.reset(TextSelection());
|
_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;
|
_link = _pressedLink = nullptr;
|
||||||
|
|
||||||
if (const auto was = base::take(_draftItem)) {
|
if (const auto was = base::take(_draftItem)) {
|
||||||
|
@ -233,10 +235,10 @@ rpl::producer<TextWithEntities> PreviewWrap::showQuoteSelector(
|
||||||
|
|
||||||
initElement();
|
initElement();
|
||||||
|
|
||||||
_selection = _element->selectionFromQuote(quote);
|
_selection = _element->selectionFromQuote(item, quote);
|
||||||
return _selection.value(
|
return _selection.value(
|
||||||
) | rpl::map([=](TextSelection selection) {
|
) | rpl::map([=](TextSelection selection) {
|
||||||
return _element->selectedQuote(selection);
|
return _element->selectedQuote(selection).text;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -48,6 +48,7 @@ struct ContextMenuRequest {
|
||||||
SelectedItems selectedItems;
|
SelectedItems selectedItems;
|
||||||
TextForMimeData selectedText;
|
TextForMimeData selectedText;
|
||||||
TextWithEntities quote;
|
TextWithEntities quote;
|
||||||
|
HistoryItem *quoteItem = nullptr;
|
||||||
bool overSelection = false;
|
bool overSelection = false;
|
||||||
PointState pointState = PointState();
|
PointState pointState = PointState();
|
||||||
};
|
};
|
||||||
|
|
|
@ -94,6 +94,42 @@ Element *MousedElement/* = nullptr*/;
|
||||||
return session->tryResolveWindow();
|
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
|
} // namespace
|
||||||
|
|
||||||
std::unique_ptr<Ui::PathShiftGradient> MakePathShiftGradient(
|
std::unique_ptr<Ui::PathShiftGradient> MakePathShiftGradient(
|
||||||
|
@ -1559,6 +1595,105 @@ TextSelection Element::adjustSelection(
|
||||||
return selection;
|
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(
|
Reactions::ButtonParameters Element::reactionButtonParameters(
|
||||||
QPoint position,
|
QPoint position,
|
||||||
const TextState &reactionState) const {
|
const TextState &reactionState) const {
|
||||||
|
|
|
@ -266,6 +266,15 @@ struct TopicButton {
|
||||||
int nameVersion = 0;
|
int nameVersion = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct SelectedQuote {
|
||||||
|
HistoryItem *item = nullptr;
|
||||||
|
TextWithEntities text;
|
||||||
|
|
||||||
|
explicit operator bool() const {
|
||||||
|
return item && !text.empty();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
class Element
|
class Element
|
||||||
: public Object
|
: public Object
|
||||||
, public RuntimeComposer<Element>
|
, public RuntimeComposer<Element>
|
||||||
|
@ -387,19 +396,24 @@ public:
|
||||||
QPoint point,
|
QPoint point,
|
||||||
InfoDisplayType type) const;
|
InfoDisplayType type) const;
|
||||||
virtual TextForMimeData selectedText(TextSelection selection) const = 0;
|
virtual TextForMimeData selectedText(TextSelection selection) const = 0;
|
||||||
virtual TextWithEntities selectedQuote(TextSelection selection) const = 0;
|
virtual SelectedQuote selectedQuote(
|
||||||
virtual TextWithEntities selectedQuote(
|
|
||||||
const Ui::Text::String &text,
|
|
||||||
TextSelection selection) const = 0;
|
TextSelection selection) const = 0;
|
||||||
virtual TextSelection selectionFromQuote(
|
virtual TextSelection selectionFromQuote(
|
||||||
const TextWithEntities "e) const = 0;
|
not_null<HistoryItem*> item,
|
||||||
virtual TextSelection selectionFromQuote(
|
|
||||||
const Ui::Text::String &text,
|
|
||||||
const TextWithEntities "e) const = 0;
|
const TextWithEntities "e) const = 0;
|
||||||
[[nodiscard]] virtual TextSelection adjustSelection(
|
[[nodiscard]] virtual TextSelection adjustSelection(
|
||||||
TextSelection selection,
|
TextSelection selection,
|
||||||
TextSelectType type) const;
|
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(
|
[[nodiscard]] virtual auto reactionButtonParameters(
|
||||||
QPoint position,
|
QPoint position,
|
||||||
const TextState &reactionState) const -> Reactions::ButtonParameters;
|
const TextState &reactionState) const -> Reactions::ButtonParameters;
|
||||||
|
|
|
@ -709,13 +709,10 @@ bool ListWidget::isBelowPosition(Data::MessagePosition position) const {
|
||||||
|
|
||||||
void ListWidget::highlightMessage(
|
void ListWidget::highlightMessage(
|
||||||
FullMsgId itemId,
|
FullMsgId itemId,
|
||||||
const TextWithEntities &highlightPart) {
|
const TextWithEntities &part) {
|
||||||
const auto view = !highlightPart.empty()
|
if (const auto view = viewForItem(itemId)) {
|
||||||
? viewForItem(itemId)
|
_highlighter.highlight(view, part);
|
||||||
: nullptr;
|
}
|
||||||
_highlighter.highlight(
|
|
||||||
itemId,
|
|
||||||
view ? view->selectionFromQuote(highlightPart) : TextSelection());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ListWidget::showAroundPosition(
|
void ListWidget::showAroundPosition(
|
||||||
|
@ -2607,9 +2604,11 @@ void ListWidget::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
|
||||||
request.view = _overElement;
|
request.view = _overElement;
|
||||||
request.item = overItem;
|
request.item = overItem;
|
||||||
request.pointState = _overState.pointState;
|
request.pointState = _overState.pointState;
|
||||||
request.quote = (overItemView && _selectedTextItem == overItem)
|
const auto quote = (overItemView && _selectedTextItem == overItem)
|
||||||
? overItemView->selectedQuote(_selectedTextRange)
|
? overItemView->selectedQuote(_selectedTextRange)
|
||||||
: TextWithEntities();
|
: SelectedQuote();
|
||||||
|
request.quote = quote.text;
|
||||||
|
request.quoteItem = quote.item;
|
||||||
request.selectedText = _selectedText;
|
request.selectedText = _selectedText;
|
||||||
request.selectedItems = collectSelectedItems();
|
request.selectedItems = collectSelectedItems();
|
||||||
const auto hasSelection = !request.selectedItems.empty()
|
const auto hasSelection = !request.selectedItems.empty()
|
||||||
|
|
|
@ -233,7 +233,7 @@ public:
|
||||||
bool isBelowPosition(Data::MessagePosition position) const;
|
bool isBelowPosition(Data::MessagePosition position) const;
|
||||||
void highlightMessage(
|
void highlightMessage(
|
||||||
FullMsgId itemId,
|
FullMsgId itemId,
|
||||||
const TextWithEntities &highlightPart);
|
const TextWithEntities &part);
|
||||||
|
|
||||||
void showAtPosition(
|
void showAtPosition(
|
||||||
Data::MessagePosition position,
|
Data::MessagePosition position,
|
||||||
|
|
|
@ -65,42 +65,6 @@ const auto kPsaTooltipPrefix = "cloud_lng_tooltip_psa_";
|
||||||
return std::nullopt;
|
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 {
|
class KeyboardStyle : public ReplyKeyboard::Style {
|
||||||
public:
|
public:
|
||||||
KeyboardStyle(const style::BotKeyboardButton &st);
|
KeyboardStyle(const style::BotKeyboardButton &st);
|
||||||
|
@ -2682,7 +2646,7 @@ TextForMimeData Message::selectedText(TextSelection selection) const {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
TextWithEntities Message::selectedQuote(TextSelection selection) const {
|
SelectedQuote Message::selectedQuote(TextSelection selection) const {
|
||||||
const auto item = data();
|
const auto item = data();
|
||||||
const auto &translated = item->translatedText();
|
const auto &translated = item->translatedText();
|
||||||
const auto &original = item->originalText();
|
const auto &original = item->originalText();
|
||||||
|
@ -2697,7 +2661,7 @@ TextWithEntities Message::selectedQuote(TextSelection selection) const {
|
||||||
const auto textSelection = mediaBefore
|
const auto textSelection = mediaBefore
|
||||||
? media->skipSelection(selection)
|
? media->skipSelection(selection)
|
||||||
: selection;
|
: selection;
|
||||||
return selectedQuote(text(), textSelection);
|
return FindSelectedQuote(text(), textSelection, data());
|
||||||
} else if (const auto media = this->media()) {
|
} else if (const auto media = this->media()) {
|
||||||
if (media->isDisplayed() || isHiddenByGroup()) {
|
if (media->isDisplayed() || isHiddenByGroup()) {
|
||||||
return media->selectedQuote(selection);
|
return media->selectedQuote(selection);
|
||||||
|
@ -2706,67 +2670,12 @@ TextWithEntities Message::selectedQuote(TextSelection selection) const {
|
||||||
return {};
|
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(
|
TextSelection Message::selectionFromQuote(
|
||||||
|
not_null<HistoryItem*> item,
|
||||||
const TextWithEntities "e) const {
|
const TextWithEntities "e) const {
|
||||||
if (quote.empty()) {
|
if (quote.empty()) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
const auto item = data();
|
|
||||||
const auto &translated = item->translatedText();
|
const auto &translated = item->translatedText();
|
||||||
const auto &original = item->originalText();
|
const auto &original = item->originalText();
|
||||||
if (&translated != &original) {
|
if (&translated != &original) {
|
||||||
|
@ -2775,58 +2684,16 @@ TextSelection Message::selectionFromQuote(
|
||||||
const auto media = this->media();
|
const auto media = this->media();
|
||||||
const auto mediaDisplayed = media && media->isDisplayed();
|
const auto mediaDisplayed = media && media->isDisplayed();
|
||||||
const auto mediaBefore = mediaDisplayed && invertMedia();
|
const auto mediaBefore = mediaDisplayed && invertMedia();
|
||||||
const auto result = selectionFromQuote(text(), quote);
|
const auto result = FindSelectionFromQuote(text(), item, quote);
|
||||||
return mediaBefore ? media->unskipSelection(result) : result;
|
return mediaBefore ? media->unskipSelection(result) : result;
|
||||||
} else if (const auto media = this->media()) {
|
} else if (const auto media = this->media()) {
|
||||||
if (media->isDisplayed() || isHiddenByGroup()) {
|
if (media->isDisplayed() || isHiddenByGroup()) {
|
||||||
return media->selectionFromQuote(quote);
|
return media->selectionFromQuote(item, quote);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {};
|
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 Message::adjustSelection(
|
||||||
TextSelection selection,
|
TextSelection selection,
|
||||||
TextSelectType type) const {
|
TextSelectType type) const {
|
||||||
|
|
|
@ -95,14 +95,9 @@ public:
|
||||||
QPoint point,
|
QPoint point,
|
||||||
InfoDisplayType type) const override;
|
InfoDisplayType type) const override;
|
||||||
TextForMimeData selectedText(TextSelection selection) const override;
|
TextForMimeData selectedText(TextSelection selection) const override;
|
||||||
TextWithEntities selectedQuote(TextSelection selection) const override;
|
SelectedQuote selectedQuote(TextSelection selection) const override;
|
||||||
TextWithEntities selectedQuote(
|
|
||||||
const Ui::Text::String &text,
|
|
||||||
TextSelection selection) const override;
|
|
||||||
TextSelection selectionFromQuote(
|
TextSelection selectionFromQuote(
|
||||||
const TextWithEntities "e) const override;
|
not_null<HistoryItem*> item,
|
||||||
TextSelection selectionFromQuote(
|
|
||||||
const Ui::Text::String &text,
|
|
||||||
const TextWithEntities "e) const override;
|
const TextWithEntities "e) const override;
|
||||||
TextSelection adjustSelection(
|
TextSelection adjustSelection(
|
||||||
TextSelection selection,
|
TextSelection selection,
|
||||||
|
|
|
@ -669,23 +669,12 @@ TextForMimeData Service::selectedText(TextSelection selection) const {
|
||||||
return text().toTextForMimeData(selection);
|
return text().toTextForMimeData(selection);
|
||||||
}
|
}
|
||||||
|
|
||||||
TextWithEntities Service::selectedQuote(TextSelection selection) const {
|
SelectedQuote Service::selectedQuote(TextSelection selection) const {
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
TextWithEntities Service::selectedQuote(
|
|
||||||
const Ui::Text::String &text,
|
|
||||||
TextSelection selection) const {
|
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
TextSelection Service::selectionFromQuote(
|
TextSelection Service::selectionFromQuote(
|
||||||
const TextWithEntities "e) const {
|
not_null<HistoryItem*> item,
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
TextSelection Service::selectionFromQuote(
|
|
||||||
const Ui::Text::String &text,
|
|
||||||
const TextWithEntities "e) const {
|
const TextWithEntities "e) const {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,14 +43,9 @@ public:
|
||||||
StateRequest request) const override;
|
StateRequest request) const override;
|
||||||
void updatePressed(QPoint point) override;
|
void updatePressed(QPoint point) override;
|
||||||
TextForMimeData selectedText(TextSelection selection) const override;
|
TextForMimeData selectedText(TextSelection selection) const override;
|
||||||
TextWithEntities selectedQuote(TextSelection selection) const override;
|
SelectedQuote selectedQuote(TextSelection selection) const override;
|
||||||
TextWithEntities selectedQuote(
|
|
||||||
const Ui::Text::String &text,
|
|
||||||
TextSelection selection) const override;
|
|
||||||
TextSelection selectionFromQuote(
|
TextSelection selectionFromQuote(
|
||||||
const TextWithEntities "e) const override;
|
not_null<HistoryItem*> item,
|
||||||
TextSelection selectionFromQuote(
|
|
||||||
const Ui::Text::String &text,
|
|
||||||
const TextWithEntities "e) const override;
|
const TextWithEntities "e) const override;
|
||||||
TextSelection adjustSelection(
|
TextSelection adjustSelection(
|
||||||
TextSelection selection,
|
TextSelection selection,
|
||||||
|
|
|
@ -1212,7 +1212,7 @@ TextForMimeData Document::selectedText(TextSelection selection) const {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
TextWithEntities Document::selectedQuote(TextSelection selection) const {
|
SelectedQuote Document::selectedQuote(TextSelection selection) const {
|
||||||
if (const auto voice = Get<HistoryDocumentVoice>()) {
|
if (const auto voice = Get<HistoryDocumentVoice>()) {
|
||||||
const auto length = voice->transcribeText.length();
|
const auto length = voice->transcribeText.length();
|
||||||
if (selection.from < length) {
|
if (selection.from < length) {
|
||||||
|
@ -1223,16 +1223,21 @@ TextWithEntities Document::selectedQuote(TextSelection selection) const {
|
||||||
voice->transcribeText);
|
voice->transcribeText);
|
||||||
}
|
}
|
||||||
if (const auto captioned = Get<HistoryDocumentCaptioned>()) {
|
if (const auto captioned = Get<HistoryDocumentCaptioned>()) {
|
||||||
return parent()->selectedQuote(captioned->caption, selection);
|
return Element::FindSelectedQuote(
|
||||||
|
captioned->caption,
|
||||||
|
selection,
|
||||||
|
_realParent);
|
||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
TextSelection Document::selectionFromQuote(
|
TextSelection Document::selectionFromQuote(
|
||||||
|
not_null<HistoryItem*> item,
|
||||||
const TextWithEntities "e) const {
|
const TextWithEntities "e) const {
|
||||||
if (const auto captioned = Get<HistoryDocumentCaptioned>()) {
|
if (const auto captioned = Get<HistoryDocumentCaptioned>()) {
|
||||||
const auto result = parent()->selectionFromQuote(
|
const auto result = Element::FindSelectionFromQuote(
|
||||||
captioned->caption,
|
captioned->caption,
|
||||||
|
item,
|
||||||
quote);
|
quote);
|
||||||
if (result.empty()) {
|
if (result.empty()) {
|
||||||
return {};
|
return {};
|
||||||
|
|
|
@ -46,8 +46,9 @@ public:
|
||||||
bool hasTextForCopy() const override;
|
bool hasTextForCopy() const override;
|
||||||
|
|
||||||
TextForMimeData selectedText(TextSelection selection) const override;
|
TextForMimeData selectedText(TextSelection selection) const override;
|
||||||
TextWithEntities selectedQuote(TextSelection selection) const override;
|
SelectedQuote selectedQuote(TextSelection selection) const override;
|
||||||
TextSelection selectionFromQuote(
|
TextSelection selectionFromQuote(
|
||||||
|
not_null<HistoryItem*> item,
|
||||||
const TextWithEntities "e) const override;
|
const TextWithEntities "e) const override;
|
||||||
|
|
||||||
bool uploading() const override;
|
bool uploading() const override;
|
||||||
|
|
|
@ -1205,13 +1205,14 @@ TextForMimeData Gif::selectedText(TextSelection selection) const {
|
||||||
return _caption.toTextForMimeData(selection);
|
return _caption.toTextForMimeData(selection);
|
||||||
}
|
}
|
||||||
|
|
||||||
TextWithEntities Gif::selectedQuote(TextSelection selection) const {
|
SelectedQuote Gif::selectedQuote(TextSelection selection) const {
|
||||||
return parent()->selectedQuote(_caption, selection);
|
return Element::FindSelectedQuote(_caption, selection, _realParent);
|
||||||
}
|
}
|
||||||
|
|
||||||
TextSelection Gif::selectionFromQuote(
|
TextSelection Gif::selectionFromQuote(
|
||||||
|
not_null<HistoryItem*> item,
|
||||||
const TextWithEntities "e) const {
|
const TextWithEntities "e) const {
|
||||||
return parent()->selectionFromQuote(_caption, quote);
|
return Element::FindSelectionFromQuote(_caption, item, quote);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Gif::fullFeaturedGrouped(RectParts sides) const {
|
bool Gif::fullFeaturedGrouped(RectParts sides) const {
|
||||||
|
|
|
@ -68,8 +68,9 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
TextForMimeData selectedText(TextSelection selection) const override;
|
TextForMimeData selectedText(TextSelection selection) const override;
|
||||||
TextWithEntities selectedQuote(TextSelection selection) const override;
|
SelectedQuote selectedQuote(TextSelection selection) const override;
|
||||||
TextSelection selectionFromQuote(
|
TextSelection selectionFromQuote(
|
||||||
|
not_null<HistoryItem*> item,
|
||||||
const TextWithEntities "e) const override;
|
const TextWithEntities "e) const override;
|
||||||
|
|
||||||
bool uploading() const override;
|
bool uploading() const override;
|
||||||
|
|
|
@ -189,6 +189,10 @@ not_null<History*> Media::history() const {
|
||||||
return _parent->history();
|
return _parent->history();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SelectedQuote Media::selectedQuote(TextSelection selection) const {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
bool Media::isDisplayed() const {
|
bool Media::isDisplayed() const {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,6 +49,7 @@ struct StateRequest;
|
||||||
struct MediaSpoiler;
|
struct MediaSpoiler;
|
||||||
class StickerPlayer;
|
class StickerPlayer;
|
||||||
class Element;
|
class Element;
|
||||||
|
struct SelectedQuote;
|
||||||
|
|
||||||
using PaintContext = Ui::ChatPaintContext;
|
using PaintContext = Ui::ChatPaintContext;
|
||||||
|
|
||||||
|
@ -88,11 +89,10 @@ public:
|
||||||
TextSelection selection) const {
|
TextSelection selection) const {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
[[nodiscard]] virtual TextWithEntities selectedQuote(
|
[[nodiscard]] virtual SelectedQuote selectedQuote(
|
||||||
TextSelection selection) const {
|
TextSelection selection) const;
|
||||||
return {};
|
|
||||||
}
|
|
||||||
[[nodiscard]] virtual TextSelection selectionFromQuote(
|
[[nodiscard]] virtual TextSelection selectionFromQuote(
|
||||||
|
not_null<HistoryItem*> item,
|
||||||
const TextWithEntities "e) const {
|
const TextWithEntities "e) const {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
|
@ -286,16 +286,44 @@ void GroupedMedia::drawHighlight(
|
||||||
Painter &p,
|
Painter &p,
|
||||||
const PaintContext &context,
|
const PaintContext &context,
|
||||||
int top) const {
|
int top) const {
|
||||||
|
auto selection = context.highlight.range;
|
||||||
if (_mode != Mode::Column) {
|
if (_mode != Mode::Column) {
|
||||||
|
if (!selection.empty() && !IsSubGroupSelection(selection)) {
|
||||||
|
_parent->paintCustomHighlight(
|
||||||
|
p,
|
||||||
|
context,
|
||||||
|
top,
|
||||||
|
height(),
|
||||||
|
_parent->data().get());
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const auto empty = selection.empty();
|
||||||
|
const auto subpart = IsSubGroupSelection(selection);
|
||||||
const auto skip = top + groupedPadding().top();
|
const auto skip = top + groupedPadding().top();
|
||||||
for (auto i = 0, count = int(_parts.size()); i != count; ++i) {
|
for (auto i = 0, count = int(_parts.size()); i != count; ++i) {
|
||||||
const auto &part = _parts[i];
|
const auto &part = _parts[i];
|
||||||
const auto rect = part.geometry.translated(0, skip);
|
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(
|
_parent->paintCustomHighlight(
|
||||||
p,
|
p,
|
||||||
context,
|
copy,
|
||||||
rect.y(),
|
rect.y(),
|
||||||
rect.height(),
|
rect.height(),
|
||||||
part.item);
|
part.item);
|
||||||
|
@ -316,6 +344,7 @@ void GroupedMedia::draw(Painter &p, const PaintContext &context) const {
|
||||||
const auto rounding = inWebPage
|
const auto rounding = inWebPage
|
||||||
? Ui::BubbleRounding{ kSmall, kSmall, kSmall, kSmall }
|
? Ui::BubbleRounding{ kSmall, kSmall, kSmall, kSmall }
|
||||||
: adjustedBubbleRoundingWithCaption(_caption);
|
: adjustedBubbleRoundingWithCaption(_caption);
|
||||||
|
const auto highlight = context.highlight.range;
|
||||||
for (auto i = 0, count = int(_parts.size()); i != count; ++i) {
|
for (auto i = 0, count = int(_parts.size()); i != count; ++i) {
|
||||||
const auto &part = _parts[i];
|
const auto &part = _parts[i];
|
||||||
const auto partContext = context.withSelection(fullSelection
|
const auto partContext = context.withSelection(fullSelection
|
||||||
|
@ -325,10 +354,11 @@ void GroupedMedia::draw(Painter &p, const PaintContext &context) const {
|
||||||
: IsGroupItemSelection(selection, i)
|
: IsGroupItemSelection(selection, i)
|
||||||
? FullSelection
|
? FullSelection
|
||||||
: TextSelection());
|
: TextSelection());
|
||||||
const auto highlightOpacity = IsGroupItemSelection(
|
const auto highlighted = (highlight.empty() && !i)
|
||||||
context.highlight.range,
|
|| IsGroupItemSelection(highlight, i);
|
||||||
i
|
const auto highlightOpacity = highlighted
|
||||||
) ? context.highlight.opacity : 0.;
|
? context.highlight.opacity
|
||||||
|
: 0.;
|
||||||
if (textSelection) {
|
if (textSelection) {
|
||||||
selection = part.content->skipSelection(selection);
|
selection = part.content->skipSelection(selection);
|
||||||
}
|
}
|
||||||
|
@ -517,6 +547,7 @@ TextSelection GroupedMedia::adjustSelection(
|
||||||
selection.to = modified.to;
|
selection.to = modified.to;
|
||||||
return selection;
|
return selection;
|
||||||
}
|
}
|
||||||
|
checked = till;
|
||||||
}
|
}
|
||||||
return selection;
|
return selection;
|
||||||
}
|
}
|
||||||
|
@ -564,6 +595,50 @@ TextForMimeData GroupedMedia::selectedText(
|
||||||
return result;
|
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(
|
auto GroupedMedia::getBubbleSelectionIntervals(
|
||||||
TextSelection selection) const
|
TextSelection selection) const
|
||||||
-> std::vector<Ui::BubbleSelectionInterval> {
|
-> std::vector<Ui::BubbleSelectionInterval> {
|
||||||
|
@ -666,16 +741,15 @@ bool GroupedMedia::validateGroupParts(
|
||||||
}
|
}
|
||||||
|
|
||||||
void GroupedMedia::refreshCaption() {
|
void GroupedMedia::refreshCaption() {
|
||||||
using PartPtrOpt = std::optional<const Part*>;
|
const auto part = [&]() -> const Part* {
|
||||||
const auto captionPart = [&]() -> PartPtrOpt {
|
|
||||||
if (_mode == Mode::Column) {
|
if (_mode == Mode::Column) {
|
||||||
return std::nullopt;
|
return nullptr;
|
||||||
}
|
}
|
||||||
auto result = PartPtrOpt();
|
auto result = (const Part*)nullptr;
|
||||||
for (const auto &part : _parts) {
|
for (const auto &part : _parts) {
|
||||||
if (!part.item->emptyText()) {
|
if (!part.item->emptyText()) {
|
||||||
if (result) {
|
if (result) {
|
||||||
return std::nullopt;
|
return nullptr;
|
||||||
} else {
|
} else {
|
||||||
result = ∂
|
result = ∂
|
||||||
}
|
}
|
||||||
|
@ -683,8 +757,7 @@ void GroupedMedia::refreshCaption() {
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}();
|
}();
|
||||||
if (captionPart) {
|
if (part) {
|
||||||
const auto &part = (*captionPart);
|
|
||||||
_caption = createCaption(part->item);
|
_caption = createCaption(part->item);
|
||||||
_captionItem = part->item;
|
_captionItem = part->item;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -55,6 +55,10 @@ public:
|
||||||
DocumentData *getDocument() const override;
|
DocumentData *getDocument() const override;
|
||||||
|
|
||||||
TextForMimeData selectedText(TextSelection selection) 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(
|
std::vector<Ui::BubbleSelectionInterval> getBubbleSelectionIntervals(
|
||||||
TextSelection selection) const override;
|
TextSelection selection) const override;
|
||||||
|
|
|
@ -1051,13 +1051,14 @@ TextForMimeData Photo::selectedText(TextSelection selection) const {
|
||||||
return _caption.toTextForMimeData(selection);
|
return _caption.toTextForMimeData(selection);
|
||||||
}
|
}
|
||||||
|
|
||||||
TextWithEntities Photo::selectedQuote(TextSelection selection) const {
|
SelectedQuote Photo::selectedQuote(TextSelection selection) const {
|
||||||
return parent()->selectedQuote(_caption, selection);
|
return Element::FindSelectedQuote(_caption, selection, _realParent);
|
||||||
}
|
}
|
||||||
|
|
||||||
TextSelection Photo::selectionFromQuote(
|
TextSelection Photo::selectionFromQuote(
|
||||||
|
not_null<HistoryItem*> item,
|
||||||
const TextWithEntities "e) const {
|
const TextWithEntities "e) const {
|
||||||
return parent()->selectionFromQuote(_caption, quote);
|
return Element::FindSelectionFromQuote(_caption, item, quote);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Photo::hideSpoilers() {
|
void Photo::hideSpoilers() {
|
||||||
|
|
|
@ -57,8 +57,9 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
TextForMimeData selectedText(TextSelection selection) const override;
|
TextForMimeData selectedText(TextSelection selection) const override;
|
||||||
TextWithEntities selectedQuote(TextSelection selection) const override;
|
SelectedQuote selectedQuote(TextSelection selection) const override;
|
||||||
TextSelection selectionFromQuote(
|
TextSelection selectionFromQuote(
|
||||||
|
not_null<HistoryItem*> item,
|
||||||
const TextWithEntities "e) const override;
|
const TextWithEntities "e) const override;
|
||||||
|
|
||||||
PhotoData *getPhoto() const override {
|
PhotoData *getPhoto() const override {
|
||||||
|
|
Loading…
Reference in New Issue