Allow to select and copy text in the events log.

Also better handle window resize in the events log.
This commit is contained in:
John Preston 2017-06-23 22:28:42 +03:00
parent 693c30d264
commit 624f33c5e2
13 changed files with 203 additions and 66 deletions

View File

@ -94,6 +94,23 @@ TextWithTags::Tags ConvertEntitiesToTextTags(const EntitiesInText &entities) {
return result; return result;
} }
std::unique_ptr<QMimeData> MimeDataFromTextWithEntities(const TextWithEntities &forClipboard) {
if (forClipboard.text.isEmpty()) {
return nullptr;
}
auto result = std::make_unique<QMimeData>();
result->setText(forClipboard.text);
auto tags = ConvertEntitiesToTextTags(forClipboard.entities);
if (!tags.isEmpty()) {
for (auto &tag : tags) {
tag.id = ConvertTagToMimeTag(tag.id);
}
result->setData(Ui::FlatTextarea::tagsMimeType(), Ui::FlatTextarea::serializeTagsList(tags));
}
return result;
}
MessageField::MessageField(QWidget *parent, gsl::not_null<Window::Controller*> controller, const style::FlatTextarea &st, base::lambda<QString()> placeholderFactory, const QString &val) : Ui::FlatTextarea(parent, st, std::move(placeholderFactory), val) MessageField::MessageField(QWidget *parent, gsl::not_null<Window::Controller*> controller, const style::FlatTextarea &st, base::lambda<QString()> placeholderFactory, const QString &val) : Ui::FlatTextarea(parent, st, std::move(placeholderFactory), val)
, _controller(controller) { , _controller(controller) {
setMinHeight(st::historySendSize.height() - 2 * st::historySendPadding); setMinHeight(st::historySendSize.height() - 2 * st::historySendPadding);

View File

@ -31,6 +31,7 @@ QString ConvertTagToMimeTag(const QString &tagId);
EntitiesInText ConvertTextTagsToEntities(const TextWithTags::Tags &tags); EntitiesInText ConvertTextTagsToEntities(const TextWithTags::Tags &tags);
TextWithTags::Tags ConvertEntitiesToTextTags(const EntitiesInText &entities); TextWithTags::Tags ConvertEntitiesToTextTags(const EntitiesInText &entities);
std::unique_ptr<QMimeData> MimeDataFromTextWithEntities(const TextWithEntities &forClipboard);
class MessageField final : public Ui::FlatTextarea { class MessageField final : public Ui::FlatTextarea {
Q_OBJECT Q_OBJECT

View File

@ -25,6 +25,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "history/history_message.h" #include "history/history_message.h"
#include "history/history_service_layout.h" #include "history/history_service_layout.h"
#include "history/history_admin_log_section.h" #include "history/history_admin_log_section.h"
#include "chat_helpers/message_field.h"
#include "mainwindow.h" #include "mainwindow.h"
#include "window/window_controller.h" #include "window/window_controller.h"
#include "auth_session.h" #include "auth_session.h"
@ -234,16 +235,20 @@ void InnerWidget::setVisibleTopBottom(int visibleTop, int visibleBottom) {
} }
void InnerWidget::updateVisibleTopItem() { void InnerWidget::updateVisibleTopItem() {
auto begin = std::rbegin(_items), end = std::rend(_items); if (_visibleBottom == height()) {
auto from = std::lower_bound(begin, end, _visibleTop, [this](auto &elem, int top) {
return itemTop(elem) + elem->height() <= top;
});
if (from != end) {
_visibleTopItem = *from;
_visibleTopFromItem = _visibleTop - _visibleTopItem->y();
} else {
_visibleTopItem = nullptr; _visibleTopItem = nullptr;
_visibleTopFromItem = _visibleTop; } else {
auto begin = std::rbegin(_items), end = std::rend(_items);
auto from = std::lower_bound(begin, end, _visibleTop, [this](auto &elem, int top) {
return itemTop(elem) + elem->height() <= top;
});
if (from != end) {
_visibleTopItem = *from;
_visibleTopFromItem = _visibleTop - _visibleTopItem->y();
} else {
_visibleTopItem = nullptr;
_visibleTopFromItem = _visibleTop;
}
} }
} }
@ -447,8 +452,7 @@ void InnerWidget::itemsAdded(Direction direction) {
void InnerWidget::updateSize() { void InnerWidget::updateSize() {
TWidget::resizeToWidth(width()); TWidget::resizeToWidth(width());
auto newVisibleTop = _visibleTopItem ? (itemTop(_visibleTopItem) + _visibleTopFromItem) : ScrollMax; restoreScrollPosition();
_scrollTo(newVisibleTop);
updateVisibleTopItem(); updateVisibleTopItem();
checkPreloadMore(); checkPreloadMore();
} }
@ -466,6 +470,11 @@ int InnerWidget::resizeGetHeight(int newWidth) {
return _itemsTop + _itemsHeight + st::historyPaddingBottom; return _itemsTop + _itemsHeight + st::historyPaddingBottom;
} }
void InnerWidget::restoreScrollPosition() {
auto newVisibleTop = _visibleTopItem ? (itemTop(_visibleTopItem) + _visibleTopFromItem) : ScrollMax;
_scrollTo(newVisibleTop);
}
void InnerWidget::paintEvent(QPaintEvent *e) { void InnerWidget::paintEvent(QPaintEvent *e) {
if (Ui::skipPaintEvent(this, e)) { if (Ui::skipPaintEvent(this, e)) {
return; return;
@ -490,7 +499,8 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
auto top = itemTop(from->get()); auto top = itemTop(from->get());
p.translate(0, top); p.translate(0, top);
for (auto i = from; i != to; ++i) { for (auto i = from; i != to; ++i) {
(*i)->draw(p, clip.translated(0, -top), TextSelection(), ms); auto selection = (*i == _selectedItem) ? _selectedText : TextSelection();
(*i)->draw(p, clip.translated(0, -top), selection, ms);
auto height = (*i)->height(); auto height = (*i)->height();
top += height; top += height;
p.translate(0, height); p.translate(0, height);
@ -562,9 +572,37 @@ void InnerWidget::paintEmpty(Painter &p) {
//p.drawText(tr.left() + st::msgPadding.left(), tr.top() + st::msgServicePadding.top() + 1 + font->ascent, lang(lng_willbe_history)); //p.drawText(tr.left() + st::msgPadding.left(), tr.top() + st::msgServicePadding.top() + 1 + font->ascent, lang(lng_willbe_history));
} }
TextWithEntities InnerWidget::getSelectedText() const {
return _selectedItem ? _selectedItem->selectedText(_selectedText) : TextWithEntities();
}
void InnerWidget::keyPressEvent(QKeyEvent *e) { void InnerWidget::keyPressEvent(QKeyEvent *e) {
if (e->key() == Qt::Key_Escape && _cancelledCallback) { if (e->key() == Qt::Key_Escape && _cancelledCallback) {
_cancelledCallback(); _cancelledCallback();
} else if (e == QKeySequence::Copy && _selectedItem != nullptr) {
copySelectedText();
#ifdef Q_OS_MAC
} else if (e->key() == Qt::Key_E && e->modifiers().testFlag(Qt::ControlModifier)) {
setToClipboard(getSelectedText(), QClipboard::FindBuffer);
#endif // Q_OS_MAC
} else {
e->ignore();
}
}
void InnerWidget::copySelectedText() {
setToClipboard(getSelectedText());
}
void InnerWidget::copyContextUrl() {
//if (_contextMenuLnk) {
// _contextMenuLnk->copyToClipboard();
//}
}
void InnerWidget::setToClipboard(const TextWithEntities &forClipboard, QClipboard::Mode mode) {
if (auto data = MimeDataFromTextWithEntities(forClipboard)) {
QApplication::clipboard()->setMimeData(data.release(), mode);
} }
} }
@ -826,7 +864,10 @@ void InnerWidget::updateSelected() {
if (dragState.afterSymbol && _mouseSelectType == TextSelectType::Letters) { if (dragState.afterSymbol && _mouseSelectType == TextSelectType::Letters) {
++second; ++second;
} }
auto selection = _mouseActionItem->adjustSelection({ qMin(second, _mouseTextSymbol), qMax(second, _mouseTextSymbol) }, _mouseSelectType); auto selection = TextSelection { qMin(second, _mouseTextSymbol), qMax(second, _mouseTextSymbol) };
if (_mouseSelectType != TextSelectType::Letters) {
_mouseActionItem->adjustSelection(selection, _mouseSelectType);
}
if (_selectedText != selection) { if (_selectedText != selection) {
_selectedText = selection; _selectedText = selection;
repaintItem(_mouseActionItem); repaintItem(_mouseActionItem);

View File

@ -68,6 +68,9 @@ public:
// Updates the area that is visible inside the scroll container. // Updates the area that is visible inside the scroll container.
void setVisibleTopBottom(int visibleTop, int visibleBottom) override; void setVisibleTopBottom(int visibleTop, int visibleBottom) override;
// Set the correct scroll position after being resized.
void restoreScrollPosition();
void resizeToWidth(int newWidth, int minHeight) { void resizeToWidth(int newWidth, int minHeight) {
_minHeight = minHeight; _minHeight = minHeight;
return TWidget::resizeToWidth(newWidth); return TWidget::resizeToWidth(newWidth);
@ -141,6 +144,11 @@ private:
void scrollDateCheck(); void scrollDateCheck();
void scrollDateHideByTimer(); void scrollDateHideByTimer();
TextWithEntities getSelectedText() const;
void copySelectedText();
void copyContextUrl();
void setToClipboard(const TextWithEntities &forClipboard, QClipboard::Mode mode = QClipboard::Clipboard);
// This function finds all history items that are displayed and calls template method // This function finds all history items that are displayed and calls template method
// for each found message (in given direction) in the passed history with passed top offset. // for each found message (in given direction) in the passed history with passed top offset.
// //

View File

@ -219,6 +219,7 @@ void Widget::resizeEvent(QResizeEvent *e) {
if (_scroll->size() != scrollSize) { if (_scroll->size() != scrollSize) {
_scroll->resize(scrollSize); _scroll->resize(scrollSize);
_inner->resizeToWidth(scrollSize.width(), _scroll->height()); _inner->resizeToWidth(scrollSize.width(), _scroll->height());
_inner->restoreScrollPosition();
} }
if (!_scroll->isHidden()) { if (!_scroll->isHidden()) {

View File

@ -78,23 +78,6 @@ int BinarySearchBlocksOrItems(const T &list, int edge) {
return start; return start;
} }
std::unique_ptr<QMimeData> MimeDataFromTextWithEntities(const TextWithEntities &forClipboard) {
if (forClipboard.text.isEmpty()) {
return nullptr;
}
auto result = std::make_unique<QMimeData>();
result->setText(forClipboard.text);
auto tags = ConvertEntitiesToTextTags(forClipboard.entities);
if (!tags.isEmpty()) {
for (auto &tag : tags) {
tag.id = ConvertTagToMimeTag(tag.id);
}
result->setData(Ui::FlatTextarea::tagsMimeType(), Ui::FlatTextarea::serializeTagsList(tags));
}
return result;
}
} // namespace } // namespace
// flick scroll taken from http://qt-project.org/doc/qt-4.8/demos-embedded-anomaly-src-flickcharm-cpp.html // flick scroll taken from http://qt-project.org/doc/qt-4.8/demos-embedded-anomaly-src-flickcharm-cpp.html
@ -2143,7 +2126,10 @@ void HistoryInner::onUpdateSelected() {
if (dragState.afterSymbol && _mouseSelectType == TextSelectType::Letters) { if (dragState.afterSymbol && _mouseSelectType == TextSelectType::Letters) {
++second; ++second;
} }
auto selState = _mouseActionItem->adjustSelection({ qMin(second, _mouseTextSymbol), qMax(second, _mouseTextSymbol) }, _mouseSelectType); auto selState = TextSelection { qMin(second, _mouseTextSymbol), qMax(second, _mouseTextSymbol) };
if (_mouseSelectType != TextSelectType::Letters) {
_mouseActionItem->adjustSelection(selState, _mouseSelectType);
}
if (_selected[_mouseActionItem] != selState) { if (_selected[_mouseActionItem] != selState) {
_selected[_mouseActionItem] = selState; _selected[_mouseActionItem] = selState;
repaintItem(_mouseActionItem); repaintItem(_mouseActionItem);

View File

@ -584,18 +584,18 @@ HistoryMediaPtr::~HistoryMediaPtr() {
namespace internal { namespace internal {
TextSelection unshiftSelection(TextSelection selection, const Text &byText) { TextSelection unshiftSelection(TextSelection selection, uint16 byLength) {
if (selection == FullSelection) { if (selection == FullSelection) {
return selection; return selection;
} }
return ::unshiftSelection(selection, byText); return ::unshiftSelection(selection, byLength);
} }
TextSelection shiftSelection(TextSelection selection, const Text &byText) { TextSelection shiftSelection(TextSelection selection, uint16 byLength) {
if (selection == FullSelection) { if (selection == FullSelection) {
return selection; return selection;
} }
return ::shiftSelection(selection, byText); return ::shiftSelection(selection, byLength);
} }
} // namespace internal } // namespace internal

View File

@ -465,8 +465,14 @@ private:
namespace internal { namespace internal {
TextSelection unshiftSelection(TextSelection selection, const Text &byText); TextSelection unshiftSelection(TextSelection selection, uint16 byLength);
TextSelection shiftSelection(TextSelection selection, const Text &byText); TextSelection shiftSelection(TextSelection selection, uint16 byLength);
inline TextSelection unshiftSelection(TextSelection selection, const Text &byText) {
return ::internal::unshiftSelection(selection, byText.length());
}
inline TextSelection shiftSelection(TextSelection selection, const Text &byText) {
return ::internal::shiftSelection(selection, byText.length());
}
} // namespace internal } // namespace internal
@ -984,10 +990,10 @@ protected:
return nullptr; return nullptr;
} }
TextSelection toMediaSelection(TextSelection selection) const { TextSelection skipTextSelection(TextSelection selection) const {
return internal::unshiftSelection(selection, _text); return internal::unshiftSelection(selection, _text);
} }
TextSelection fromMediaSelection(TextSelection selection) const { TextSelection unskipTextSelection(TextSelection selection) const {
return internal::shiftSelection(selection, _text); return internal::shiftSelection(selection, _text);
} }

View File

@ -90,6 +90,15 @@ public:
virtual bool consumeMessageText(const TextWithEntities &textWithEntities) { virtual bool consumeMessageText(const TextWithEntities &textWithEntities) {
return false; return false;
} }
virtual uint16 fullSelectionLength() const {
return 0;
}
TextSelection skipSelection(TextSelection selection) const {
return internal::unshiftSelection(selection, fullSelectionLength());
}
TextSelection unskipSelection(TextSelection selection) const {
return internal::shiftSelection(selection, fullSelectionLength());
}
// if we press and drag this link should we drag the item // if we press and drag this link should we drag the item
virtual bool dragItemByHandler(const ClickHandlerPtr &p) const = 0; virtual bool dragItemByHandler(const ClickHandlerPtr &p) const = 0;

View File

@ -139,6 +139,9 @@ public:
TextSelection adjustSelection(TextSelection selection, TextSelectType type) const override { TextSelection adjustSelection(TextSelection selection, TextSelectType type) const override {
return _caption.adjustSelection(selection, type); return _caption.adjustSelection(selection, type);
} }
uint16 fullSelectionLength() const override {
return _caption.length();
}
bool hasTextForCopy() const override { bool hasTextForCopy() const override {
return !_caption.isEmpty(); return !_caption.isEmpty();
} }
@ -221,6 +224,9 @@ public:
TextSelection adjustSelection(TextSelection selection, TextSelectType type) const override { TextSelection adjustSelection(TextSelection selection, TextSelectType type) const override {
return _caption.adjustSelection(selection, type); return _caption.adjustSelection(selection, type);
} }
uint16 fullSelectionLength() const override {
return _caption.length();
}
bool hasTextForCopy() const override { bool hasTextForCopy() const override {
return !_caption.isEmpty(); return !_caption.isEmpty();
} }
@ -372,6 +378,12 @@ public:
} }
return selection; return selection;
} }
uint16 fullSelectionLength() const override {
if (auto captioned = Get<HistoryDocumentCaptioned>()) {
return captioned->_caption.length();
}
return 0;
}
bool hasTextForCopy() const override { bool hasTextForCopy() const override {
return Has<HistoryDocumentCaptioned>(); return Has<HistoryDocumentCaptioned>();
} }
@ -475,6 +487,9 @@ public:
TextSelection adjustSelection(TextSelection selection, TextSelectType type) const override { TextSelection adjustSelection(TextSelection selection, TextSelectType type) const override {
return _caption.adjustSelection(selection, type); return _caption.adjustSelection(selection, type);
} }
uint16 fullSelectionLength() const override {
return _caption.length();
}
bool hasTextForCopy() const override { bool hasTextForCopy() const override {
return !_caption.isEmpty(); return !_caption.isEmpty();
} }
@ -769,6 +784,9 @@ public:
HistoryTextState getState(QPoint point, HistoryStateRequest request) const override; HistoryTextState getState(QPoint point, HistoryStateRequest request) const override;
TextSelection adjustSelection(TextSelection selection, TextSelectType type) const override; TextSelection adjustSelection(TextSelection selection, TextSelectType type) const override;
uint16 fullSelectionLength() const override {
return _title.length() + _description.length();
}
bool hasTextForCopy() const override { bool hasTextForCopy() const override {
return false; // we do not add _title and _description in FullSelection text copy. return false; // we do not add _title and _description in FullSelection text copy.
} }
@ -869,6 +887,9 @@ public:
HistoryTextState getState(QPoint point, HistoryStateRequest request) const override; HistoryTextState getState(QPoint point, HistoryStateRequest request) const override;
TextSelection adjustSelection(TextSelection selection, TextSelectType type) const override; TextSelection adjustSelection(TextSelection selection, TextSelectType type) const override;
uint16 fullSelectionLength() const override {
return _title.length() + _description.length();
}
bool isAboveMessage() const override { bool isAboveMessage() const override {
return true; return true;
} }
@ -977,6 +998,9 @@ public:
HistoryTextState getState(QPoint point, HistoryStateRequest request) const override; HistoryTextState getState(QPoint point, HistoryStateRequest request) const override;
TextSelection adjustSelection(TextSelection selection, TextSelectType type) const override; TextSelection adjustSelection(TextSelection selection, TextSelectType type) const override;
uint16 fullSelectionLength() const override {
return _title.length() + _description.length();
}
bool hasTextForCopy() const override { bool hasTextForCopy() const override {
return false; // we do not add _title and _description in FullSelection text copy. return false; // we do not add _title and _description in FullSelection text copy.
} }
@ -1060,6 +1084,9 @@ public:
HistoryTextState getState(QPoint point, HistoryStateRequest request) const override; HistoryTextState getState(QPoint point, HistoryStateRequest request) const override;
TextSelection adjustSelection(TextSelection selection, TextSelectType type) const override; TextSelection adjustSelection(TextSelection selection, TextSelectType type) const override;
uint16 fullSelectionLength() const override {
return _title.length() + _description.length();
}
bool hasTextForCopy() const override { bool hasTextForCopy() const override {
return !_title.isEmpty() || !_description.isEmpty(); return !_title.isEmpty() || !_description.isEmpty();
} }

View File

@ -1058,24 +1058,33 @@ void HistoryMessage::eraseFromOverview() {
} }
TextWithEntities HistoryMessage::selectedText(TextSelection selection) const { TextWithEntities HistoryMessage::selectedText(TextSelection selection) const {
TextWithEntities result, textResult, mediaResult; TextWithEntities textResult, mediaResult, logEntryOriginalResult;
if (selection == FullSelection) { if (selection == FullSelection) {
textResult = _text.originalTextWithEntities(AllTextSelection, ExpandLinksAll); textResult = _text.originalTextWithEntities(AllTextSelection, ExpandLinksAll);
} else { } else {
textResult = _text.originalTextWithEntities(selection, ExpandLinksAll); textResult = _text.originalTextWithEntities(selection, ExpandLinksAll);
} }
if (_media) { auto skipped = skipTextSelection(selection);
mediaResult = _media->selectedText(toMediaSelection(selection)); auto mediaDisplayed = (_media && _media->isDisplayed());
if (mediaDisplayed) {
mediaResult = _media->selectedText(skipped);
} }
if (textResult.text.isEmpty()) { if (auto entry = Get<HistoryMessageLogEntryOriginal>()) {
result = mediaResult; logEntryOriginalResult = entry->_page->selectedText(mediaDisplayed ? _media->skipSelection(skipped) : skipped);
} else if (mediaResult.text.isEmpty()) { }
result = textResult; auto result = textResult;
} else { if (result.text.isEmpty()) {
result.text = textResult.text + qstr("\n\n"); result = std::move(mediaResult);
result.entities = textResult.entities; } else if (!mediaResult.text.isEmpty()) {
result.text += qstr("\n\n");
appendTextWithEntities(result, std::move(mediaResult)); appendTextWithEntities(result, std::move(mediaResult));
} }
if (result.text.isEmpty()) {
result = std::move(logEntryOriginalResult);
} else if (!logEntryOriginalResult.text.isEmpty()) {
result.text += qstr("\n\n");
appendTextWithEntities(result, std::move(logEntryOriginalResult));
}
if (auto forwarded = Get<HistoryMessageForwarded>()) { if (auto forwarded = Get<HistoryMessageForwarded>()) {
if (selection == FullSelection) { if (selection == FullSelection) {
auto fwdinfo = forwarded->_text.originalTextWithEntities(AllTextSelection, ExpandLinksAll); auto fwdinfo = forwarded->_text.originalTextWithEntities(AllTextSelection, ExpandLinksAll);
@ -1138,7 +1147,8 @@ void HistoryMessage::setText(const TextWithEntities &textWithEntities) {
if (mediaDisplayed && _media->consumeMessageText(textWithEntities)) { if (mediaDisplayed && _media->consumeMessageText(textWithEntities)) {
setEmptyText(); setEmptyText();
} else { } else {
if (_media && _media->isDisplayed() && !_media->isAboveMessage()) { auto mediaOnBottom = (_media && _media->isDisplayed() && _media->isBubbleBottom()) || Has<HistoryMessageLogEntryOriginal>();
if (mediaOnBottom) {
_text.setMarkedText(st::messageTextStyle, textWithEntities, itemTextOptions(this)); _text.setMarkedText(st::messageTextStyle, textWithEntities, itemTextOptions(this));
} else { } else {
_text.setMarkedText(st::messageTextStyle, { textWithEntities.text + skipBlock(), textWithEntities.entities }, itemTextOptions(this)); _text.setMarkedText(st::messageTextStyle, { textWithEntities.text + skipBlock(), textWithEntities.entities }, itemTextOptions(this));
@ -1442,7 +1452,7 @@ void HistoryMessage::draw(Painter &p, QRect clip, TextSelection selection, TimeM
paintText(p, trect, selection); paintText(p, trect, selection);
} }
p.translate(mediaLeft, mediaTop); p.translate(mediaLeft, mediaTop);
_media->draw(p, clip.translated(-mediaLeft, -mediaTop), toMediaSelection(selection), ms); _media->draw(p, clip.translated(-mediaLeft, -mediaTop), skipTextSelection(selection), ms);
p.translate(-mediaLeft, -mediaTop); p.translate(-mediaLeft, -mediaTop);
if (mediaAboveText) { if (mediaAboveText) {
@ -1458,7 +1468,11 @@ void HistoryMessage::draw(Painter &p, QRect clip, TextSelection selection, TimeM
auto entryLeft = g.left(); auto entryLeft = g.left();
auto entryTop = trect.y() + trect.height(); auto entryTop = trect.y() + trect.height();
p.translate(entryLeft, entryTop); p.translate(entryLeft, entryTop);
entry->_page->draw(p, clip.translated(-entryLeft, -entryTop), TextSelection(), ms); auto entrySelection = skipTextSelection(selection);
if (mediaDisplayed) {
entrySelection = _media->skipSelection(entrySelection);
}
entry->_page->draw(p, clip.translated(-entryLeft, -entryTop), entrySelection, ms);
p.translate(-entryLeft, -entryTop); p.translate(-entryLeft, -entryTop);
} }
if (needDrawInfo) { if (needDrawInfo) {
@ -1466,7 +1480,7 @@ void HistoryMessage::draw(Painter &p, QRect clip, TextSelection selection, TimeM
} }
} else if (_media) { } else if (_media) {
p.translate(g.topLeft()); p.translate(g.topLeft());
_media->draw(p, clip.translated(-g.topLeft()), toMediaSelection(selection), ms); _media->draw(p, clip.translated(-g.topLeft()), skipTextSelection(selection), ms);
p.translate(-g.topLeft()); p.translate(-g.topLeft());
} }
@ -1749,7 +1763,7 @@ HistoryTextState HistoryMessage::getState(QPoint point, HistoryStateRequest requ
auto entryTop = trect.y() + trect.height(); auto entryTop = trect.y() + trect.height();
if (point.y() >= entryTop && point.y() < entryTop + entryHeight) { if (point.y() >= entryTop && point.y() < entryTop + entryHeight) {
result = entry->_page->getState(point - QPoint(entryLeft, entryTop), request); result = entry->_page->getState(point - QPoint(entryLeft, entryTop), request);
result.symbol += _text.length(); result.symbol += _text.length() + (mediaDisplayed ? _media->fullSelectionLength() : 0);
} }
} }
@ -1926,15 +1940,38 @@ bool HistoryMessage::getStateText(QPoint point, QRect &trect, HistoryTextState *
} }
TextSelection HistoryMessage::adjustSelection(TextSelection selection, TextSelectType type) const { TextSelection HistoryMessage::adjustSelection(TextSelection selection, TextSelectType type) const {
if (!_media || selection.to <= _text.length()) { auto result = _text.adjustSelection(selection, type);
return _text.adjustSelection(selection, type); auto beforeMediaLength = _text.length();
if (selection.to <= beforeMediaLength) {
return result;
} }
auto mediaSelection = _media->adjustSelection(toMediaSelection(selection), type); auto mediaDisplayed = _media && _media->isDisplayed();
if (selection.from >= _text.length()) { if (mediaDisplayed) {
return fromMediaSelection(mediaSelection); auto mediaSelection = unskipTextSelection(_media->adjustSelection(skipTextSelection(selection), type));
if (selection.from >= beforeMediaLength) {
result = mediaSelection;
} else {
result.to = mediaSelection.to;
}
} }
auto textSelection = _text.adjustSelection(selection, type); auto beforeEntryLength = beforeMediaLength + (mediaDisplayed ? _media->fullSelectionLength() : 0);
return { textSelection.from, fromMediaSelection(mediaSelection).to }; if (selection.to <= beforeEntryLength) {
return result;
}
if (auto entry = Get<HistoryMessageLogEntryOriginal>()) {
auto entrySelection = mediaDisplayed ? _media->skipSelection(skipTextSelection(selection)) : skipTextSelection(selection);
auto logEntryOriginalSelection = entry->_page->adjustSelection(entrySelection, type);
if (mediaDisplayed) {
logEntryOriginalSelection = _media->unskipSelection(logEntryOriginalSelection);
}
logEntryOriginalSelection = unskipTextSelection(logEntryOriginalSelection);
if (selection.from >= beforeEntryLength) {
result = logEntryOriginalSelection;
} else {
result.to = logEntryOriginalSelection.to;
}
}
return result;
} }
void HistoryMessage::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) { void HistoryMessage::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) {

View File

@ -218,7 +218,7 @@ void ServiceMessagePainter::paint(Painter &p, const HistoryService *message, con
height -= st::msgServiceMargin.top() + media->height(); height -= st::msgServiceMargin.top() + media->height();
auto left = st::msgServiceMargin.left() + (g.width() - media->maxWidth()) / 2, top = st::msgServiceMargin.top() + height + st::msgServiceMargin.top(); auto left = st::msgServiceMargin.left() + (g.width() - media->maxWidth()) / 2, top = st::msgServiceMargin.top() + height + st::msgServiceMargin.top();
p.translate(left, top); p.translate(left, top);
media->draw(p, context.clip.translated(-left, -top), message->toMediaSelection(context.selection), context.ms); media->draw(p, context.clip.translated(-left, -top), message->skipTextSelection(context.selection), context.ms);
p.translate(-left, -top); p.translate(-left, -top);
} }

View File

@ -245,13 +245,17 @@ private:
inline TextSelection snapSelection(int from, int to) { inline TextSelection snapSelection(int from, int to) {
return { static_cast<uint16>(snap(from, 0, 0xFFFF)), static_cast<uint16>(snap(to, 0, 0xFFFF)) }; return { static_cast<uint16>(snap(from, 0, 0xFFFF)), static_cast<uint16>(snap(to, 0, 0xFFFF)) };
} }
inline TextSelection shiftSelection(TextSelection selection, uint16 byLength) {
return snapSelection(int(selection.from) + byLength, int(selection.to) + byLength);
}
inline TextSelection unshiftSelection(TextSelection selection, uint16 byLength) {
return snapSelection(int(selection.from) - int(byLength), int(selection.to) - int(byLength));
}
inline TextSelection shiftSelection(TextSelection selection, const Text &byText) { inline TextSelection shiftSelection(TextSelection selection, const Text &byText) {
int len = byText.length(); return shiftSelection(selection, byText.length());
return snapSelection(int(selection.from) + len, int(selection.to) + len);
} }
inline TextSelection unshiftSelection(TextSelection selection, const Text &byText) { inline TextSelection unshiftSelection(TextSelection selection, const Text &byText) {
int len = byText.length(); return unshiftSelection(selection, byText.length());
return snapSelection(int(selection.from) - len, int(selection.to) - len);
} }
void initLinkSets(); void initLinkSets();