From 537400d8b245bc5fa112bab1e0123b54caa565a8 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 15 Dec 2017 19:25:47 +0300 Subject: [PATCH] Enable distinct selecting of grouped media. --- Telegram/SourceFiles/data/data_document.cpp | 25 +- Telegram/SourceFiles/data/data_document.h | 19 +- Telegram/SourceFiles/data/data_photo.cpp | 12 +- Telegram/SourceFiles/data/data_photo.h | 11 +- .../history/history_inner_widget.cpp | 384 +++++++++++++----- .../history/history_inner_widget.h | 29 +- Telegram/SourceFiles/history/history_item.cpp | 23 ++ Telegram/SourceFiles/history/history_item.h | 34 +- Telegram/SourceFiles/history/history_media.h | 3 +- .../history/history_media_grouped.cpp | 105 ++++- .../history/history_media_grouped.h | 5 +- .../history/history_media_types.cpp | 197 ++++++--- .../SourceFiles/history/history_media_types.h | 91 +++-- .../SourceFiles/history/history_message.cpp | 68 +++- .../SourceFiles/history/history_message.h | 27 +- .../SourceFiles/history/history_service.cpp | 7 +- .../inline_bot_layout_internal.cpp | 32 +- Telegram/SourceFiles/layout.h | 34 ++ Telegram/SourceFiles/messenger.cpp | 12 +- Telegram/SourceFiles/messenger.h | 4 +- .../SourceFiles/overview/overview_layout.cpp | 60 +-- .../SourceFiles/settings/settings_cover.cpp | 4 +- 22 files changed, 880 insertions(+), 306 deletions(-) diff --git a/Telegram/SourceFiles/data/data_document.cpp b/Telegram/SourceFiles/data/data_document.cpp index 8df45de7c7..ecb2774619 100644 --- a/Telegram/SourceFiles/data/data_document.cpp +++ b/Telegram/SourceFiles/data/data_document.cpp @@ -224,7 +224,10 @@ QString documentSaveFilename(const DocumentData *data, bool forceSavingAs = fals return saveFileName(caption, filter, prefix, name, forceSavingAs, dir); } -void DocumentOpenClickHandler::doOpen(DocumentData *data, HistoryItem *context, ActionOnLoad action) { +void DocumentOpenClickHandler::doOpen( + not_null data, + HistoryItem *context, + ActionOnLoad action) { if (!data->date) return; auto msgId = context ? context->fullId() : FullMsgId(); @@ -329,9 +332,13 @@ void DocumentOpenClickHandler::doOpen(DocumentData *data, HistoryItem *context, } void DocumentOpenClickHandler::onClickImpl() const { - const auto item = App::hoveredLinkItem() + const auto item = context() + ? App::histItemById(context()) + : App::hoveredLinkItem() ? App::hoveredLinkItem() - : (App::contextItem() ? App::contextItem() : nullptr); + : App::contextItem() + ? App::contextItem() + : nullptr; const auto action = document()->isVoiceMessage() ? ActionOnLoadNone : ActionOnLoadOpen; @@ -339,13 +346,19 @@ void DocumentOpenClickHandler::onClickImpl() const { } void GifOpenClickHandler::onClickImpl() const { - const auto item = App::hoveredLinkItem() + const auto item = context() + ? App::histItemById(context()) + : App::hoveredLinkItem() ? App::hoveredLinkItem() - : (App::contextItem() ? App::contextItem() : nullptr); + : App::contextItem() + ? App::contextItem() + : nullptr; doOpen(document(), item, ActionOnLoadPlayInline); } -void DocumentSaveClickHandler::doSave(DocumentData *data, bool forceSavingAs) { +void DocumentSaveClickHandler::doSave( + not_null data, + bool forceSavingAs) { if (!data->date) return; auto filepath = data->filepath(DocumentData::FilePathResolveSaveFromDataSilent, forceSavingAs); diff --git a/Telegram/SourceFiles/data/data_document.h b/Telegram/SourceFiles/data/data_document.h index f2360142ac..a1e1e0e04f 100644 --- a/Telegram/SourceFiles/data/data_document.h +++ b/Telegram/SourceFiles/data/data_document.h @@ -311,15 +311,22 @@ QByteArray documentWaveformEncode5bit(const VoiceWaveform &waveform); class DocumentClickHandler : public LeftButtonClickHandler { public: - DocumentClickHandler(DocumentData *document) - : _document(document) { + DocumentClickHandler( + not_null document, + FullMsgId context = FullMsgId()) + : _document(document) + , _context(context) { } - DocumentData *document() const { + not_null document() const { return _document; } + FullMsgId context() const { + return _context; + } private: - DocumentData *_document; + not_null _document; + FullMsgId _context; }; @@ -327,7 +334,7 @@ class DocumentSaveClickHandler : public DocumentClickHandler { public: using DocumentClickHandler::DocumentClickHandler; static void doSave( - DocumentData *document, + not_null document, bool forceSavingAs = false); protected: @@ -339,7 +346,7 @@ class DocumentOpenClickHandler : public DocumentClickHandler { public: using DocumentClickHandler::DocumentClickHandler; static void doOpen( - DocumentData *document, + not_null document, HistoryItem *context, ActionOnLoad action = ActionOnLoadOpen); diff --git a/Telegram/SourceFiles/data/data_photo.cpp b/Telegram/SourceFiles/data/data_photo.cpp index 41359df524..9e1498d18a 100644 --- a/Telegram/SourceFiles/data/data_photo.cpp +++ b/Telegram/SourceFiles/data/data_photo.cpp @@ -121,7 +121,7 @@ ImagePtr PhotoData::makeReplyPreview() { } void PhotoOpenClickHandler::onClickImpl() const { - Messenger::Instance().showPhoto(this, App::hoveredLinkItem() ? App::hoveredLinkItem() : App::contextItem()); + Messenger::Instance().showPhoto(this); } void PhotoSaveClickHandler::onClickImpl() const { @@ -136,13 +136,9 @@ void PhotoCancelClickHandler::onClickImpl() const { if (!data->date) return; if (data->uploading()) { - if (auto item = App::hoveredLinkItem() ? App::hoveredLinkItem() : (App::contextItem() ? App::contextItem() : nullptr)) { - if (auto media = item->getMedia()) { - if (media->type() == MediaTypePhoto && static_cast(media)->photo() == data) { - App::contextItem(item); - App::main()->cancelUploadLayer(); - } - } + if (const auto item = App::histItemById(context())) { + App::contextItem(item); + App::main()->cancelUploadLayer(); } } else { data->cancel(); diff --git a/Telegram/SourceFiles/data/data_photo.h b/Telegram/SourceFiles/data/data_photo.h index 3fea502d51..d2d3630dd4 100644 --- a/Telegram/SourceFiles/data/data_photo.h +++ b/Telegram/SourceFiles/data/data_photo.h @@ -74,8 +74,11 @@ class PhotoClickHandler : public LeftButtonClickHandler { public: PhotoClickHandler( not_null photo, + FullMsgId context = FullMsgId(), PeerData *peer = nullptr) - : _photo(photo), _peer(peer) { + : _photo(photo) + , _context(context) + , _peer(peer) { } not_null photo() const { return _photo; @@ -83,10 +86,14 @@ public: PeerData *peer() const { return _peer; } + FullMsgId context() const { + return _context; + } private: not_null _photo; - PeerData *_peer; + FullMsgId _context; + PeerData *_peer = nullptr; }; diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index 50ac441106..375f2b17af 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -363,6 +363,55 @@ void HistoryInner::enumerateDates(Method method) { enumerateItems(dateCallback); } +TextSelection HistoryInner::itemRenderSelection( + not_null item, + int selfromy, + int seltoy) const { + Expects(!item->detached()); + + const auto y = item->block()->y() + item->y(); + if (y >= selfromy && y < seltoy) { + if (_dragSelecting && !item->serviceMsg() && item->id > 0) { + return FullSelection; + } + } else if (!_selected.empty()) { + const auto itemSelection = [&](not_null item) { + auto i = _selected.find(item); + if (i != _selected.end()) { + return i->second; + } + return TextSelection(); + }; + const auto group = item->Get(); + if (group) { + if (group->leader != item) { + return TextSelection(); + } + auto result = TextSelection(); + auto allFullSelected = true; + const auto count = int(group->others.size()); + for (auto i = 0; i != count; ++i) { + if (itemSelection(group->others[i]) == FullSelection) { + result = AddGroupItemSelection(result, i); + } else { + allFullSelected = false; + } + } + const auto leaderSelection = itemSelection(item); + if (leaderSelection == FullSelection) { + return allFullSelected + ? FullSelection + : AddGroupItemSelection(result, count); + } else if (leaderSelection != TextSelection()) { + return leaderSelection; + } + return result; + } + return itemSelection(item); + } + return TextSelection(); +} + void HistoryInner::paintEvent(QPaintEvent *e) { if (Ui::skipPaintEvent(this, e)) { return; @@ -399,9 +448,6 @@ void HistoryInner::paintEvent(QPaintEvent *e) { adjustCurrent(clip.top()); - auto selEnd = _selected.cend(); - auto hasSel = !_selected.empty(); - auto drawToY = clip.y() + clip.height(); auto selfromy = itemTop(_dragSelFrom); @@ -425,18 +471,11 @@ void HistoryInner::paintEvent(QPaintEvent *e) { p.save(); p.translate(0, y); if (clip.y() < y + item->height()) while (y < drawToY) { - TextSelection sel; - if (y >= selfromy && y < seltoy) { - if (_dragSelecting && !item->serviceMsg() && item->id > 0) { - sel = FullSelection; - } - } else if (hasSel) { - auto i = _selected.find(item); - if (i != selEnd) { - sel = i->second; - } - } - item->draw(p, clip.translated(0, -y), sel, ms); + const auto selection = itemRenderSelection( + item, + selfromy - mtop, + seltoy - mtop); + item->draw(p, clip.translated(0, -y), selection, ms); if (item->hasViews()) { App::main()->scheduleViewIncrement(item); @@ -469,25 +508,18 @@ void HistoryInner::paintEvent(QPaintEvent *e) { auto iItem = (_curHistory == _history ? _curItem : 0); auto item = block->items[iItem]; - auto historyRect = clip.intersected(QRect(0, hdrawtop, width(), clip.top() + clip.height())); + auto hclip = clip.intersected(QRect(0, hdrawtop, width(), clip.top() + clip.height())); auto y = htop + block->y() + item->y(); p.save(); p.translate(0, y); while (y < drawToY) { auto h = item->height(); - if (historyRect.y() < y + h && hdrawtop < y + h) { - TextSelection sel; - if (y >= selfromy && y < seltoy) { - if (_dragSelecting && !item->serviceMsg() && item->id > 0) { - sel = FullSelection; - } - } else if (hasSel) { - auto i = _selected.find(item); - if (i != selEnd) { - sel = i->second; - } - } - item->draw(p, historyRect.translated(0, -y), sel, ms); + if (hclip.y() < y + h && hdrawtop < y + h) { + const auto selection = itemRenderSelection( + item, + selfromy - htop, + seltoy - htop); + item->draw(p, hclip.translated(0, -y), selection, ms); if (item->hasViews()) { App::main()->scheduleViewIncrement(item); @@ -830,7 +862,9 @@ void HistoryInner::mouseActionStart(const QPoint &screenPos, Qt::MouseButton but _mouseAction = MouseAction::PrepareDrag; } else if (!_selected.empty()) { if (_selected.cbegin()->second == FullSelection) { - if (_selected.find(_mouseActionItem) != _selected.cend() && App::hoveredItem()) { + if (_dragStateItem + && _selected.find(_dragStateItem) != _selected.cend() + && App::hoveredItem()) { _mouseAction = MouseAction::PrepareDrag; // start items drag } else if (!_pressWasInactive) { _mouseAction = MouseAction::PrepareSelect; // start items select @@ -915,6 +949,7 @@ void HistoryInner::mouseActionStart(const QPoint &screenPos, Qt::MouseButton but void HistoryInner::mouseActionCancel() { _mouseActionItem = nullptr; + _dragStateItem = nullptr; _mouseAction = MouseAction::None; _dragStartPosition = QPoint(0, 0); _dragSelFrom = _dragSelTo = nullptr; @@ -928,7 +963,8 @@ void HistoryInner::performDrag() { bool uponSelected = false; if (_mouseActionItem) { if (!_selected.empty() && _selected.cbegin()->second == FullSelection) { - uponSelected = (_selected.find(_mouseActionItem) != _selected.cend()); + uponSelected = _dragStateItem + && (_selected.find(_dragStateItem) != _selected.cend()); } else { HistoryStateRequest request; request.flags |= Text::StateRequest::Flag::LookupSymbol; @@ -986,7 +1022,7 @@ void HistoryInner::performDrag() { forwardMimeType = qsl("application/x-td-forward-pressed"); } } - if (auto pressedLnkItem = App::pressedLinkItem()) { + if (const auto pressedLnkItem = _dragStateItem) { if ((pressedMedia = pressedLnkItem->getMedia())) { if (forwardMimeType.isEmpty() && pressedMedia->dragItemByHandler(pressedHandler)) { forwardMimeType = qsl("application/x-td-forward-pressed-link"); @@ -1029,6 +1065,9 @@ void HistoryInner::itemRemoved(not_null item) { if (_mouseActionItem == item) { mouseActionCancel(); } + if (_dragStateItem == item) { + _dragStateItem = nullptr; + } if (_dragSelFrom == item || _dragSelTo == item) { _dragSelFrom = 0; @@ -1041,15 +1080,14 @@ void HistoryInner::itemRemoved(not_null item) { void HistoryInner::mouseActionFinish(const QPoint &screenPos, Qt::MouseButton button) { mouseActionUpdate(screenPos); - auto pressedLinkItem = App::pressedLinkItem(); auto activated = ClickHandler::unpressed(); if (_mouseAction == MouseAction::Dragging) { activated.clear(); - } else if (auto pressed = pressedLinkItem) { + } else if (_mouseActionItem) { // if we are in selecting items mode perhaps we want to // toggle selection instead of activating the pressed link if (_mouseAction == MouseAction::PrepareDrag && !_pressWasInactive && !_selected.empty() && _selected.cbegin()->second == FullSelection && button != Qt::RightButton) { - if (auto media = pressed->getMedia()) { + if (auto media = _mouseActionItem->getMedia()) { if (media->toggleSelectionByHandlerClick(activated)) { activated.clear(); } @@ -1068,29 +1106,30 @@ void HistoryInner::mouseActionFinish(const QPoint &screenPos, Qt::MouseButton bu App::activateClickHandler(activated, button); return; } - if (_mouseAction == MouseAction::PrepareSelect && !_pressWasInactive && !_selected.empty() && _selected.cbegin()->second == FullSelection) { - auto i = _selected.find(_mouseActionItem); - if (i == _selected.cend()) { - if (!_mouseActionItem->serviceMsg() - && IsServerMsgId(_mouseActionItem->id) - && _selected.size() < MaxSelectedItems) { - if (!_selected.empty() && _selected.cbegin()->second != FullSelection) { - _selected.clear(); - } - _selected.emplace(_mouseActionItem, FullSelection); - } - } else { - _selected.erase(i); - } + if ((_mouseAction == MouseAction::PrepareSelect) + && !_pressWasInactive + && !_selected.empty() + && (_selected.cbegin()->second == FullSelection)) { + changeDragSelection( + &_selected, + _mouseActionItem, + SelectAction::Invert); repaintItem(_mouseActionItem); - } else if (_mouseAction == MouseAction::PrepareDrag && !_pressWasInactive && button != Qt::RightButton) { - auto i = _selected.find(_mouseActionItem); + } else if ((_mouseAction == MouseAction::PrepareDrag) + && !_pressWasInactive + && _dragStateItem + && (button != Qt::RightButton)) { + auto i = _selected.find(_dragStateItem); if (i != _selected.cend() && i->second == FullSelection) { _selected.erase(i); repaintItem(_mouseActionItem); - } else if (i == _selected.cend() && !_mouseActionItem->serviceMsg() && _mouseActionItem->id > 0 && !_selected.empty() && _selected.cbegin()->second == FullSelection) { + } else if ((i == _selected.cend()) + && !_dragStateItem->serviceMsg() + && (_dragStateItem->id > 0) + && !_selected.empty() + && _selected.cbegin()->second == FullSelection) { if (_selected.size() < MaxSelectedItems) { - _selected.emplace(_mouseActionItem, FullSelection); + _selected.emplace(_dragStateItem, FullSelection); repaintItem(_mouseActionItem); } } else { @@ -1117,7 +1156,8 @@ void HistoryInner::mouseActionFinish(const QPoint &screenPos, Qt::MouseButton bu #if defined Q_OS_LINUX32 || defined Q_OS_LINUX64 if (!_selected.empty() && _selected.cbegin()->second != FullSelection) { - setToClipboard(_selected.cbegin()->first->selectedText(_selected.cbegin()->second), QClipboard::Selection); + const auto [item, selection] = *_selected.cbegin(); + setToClipboard(item->selectedText(selection), QClipboard::Selection); } #endif // Q_OS_LINUX32 || Q_OS_LINUX64 } @@ -1206,9 +1246,9 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { _menu = new Ui::PopupMenu(nullptr); _contextMenuLink = ClickHandler::getActive(); - HistoryItem *item = App::hoveredItem() ? App::hoveredItem() : App::hoveredLinkItem(); - PhotoClickHandler *lnkPhoto = dynamic_cast(_contextMenuLink.data()); - DocumentClickHandler *lnkDocument = dynamic_cast(_contextMenuLink.data()); + auto item = App::hoveredItem() ? App::hoveredItem() : App::hoveredLinkItem(); + auto lnkPhoto = dynamic_cast(_contextMenuLink.data()); + auto lnkDocument = dynamic_cast(_contextMenuLink.data()); auto lnkIsVideo = lnkDocument ? lnkDocument->document()->isVideoFile() : false; auto lnkIsVoice = lnkDocument ? lnkDocument->document()->isVoiceMessage() : false; auto lnkIsAudio = lnkDocument ? lnkDocument->document()->isAudioFile() : false; @@ -1279,7 +1319,9 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { } } if (App::hoveredLinkItem()->id > 0 && !App::hoveredLinkItem()->serviceMsg()) { - _menu->addAction(lang(lng_context_select_msg), _widget, SLOT(selectMessage()))->setEnabled(true); + _menu->addAction(lang(lng_context_select_msg), base::lambda_guarded(this, [this] { + // TODO + }))->setEnabled(true); } App::contextItem(App::hoveredLinkItem()); } @@ -1513,7 +1555,7 @@ void HistoryInner::saveContextGif() { } void HistoryInner::copyContextText() { - auto item = App::contextItem(); + const auto item = App::contextItem(); if (!item || (item->getMedia() && item->getMedia()->type() == MediaTypeSticker)) { return; } @@ -1532,24 +1574,24 @@ void HistoryInner::resizeEvent(QResizeEvent *e) { } TextWithEntities HistoryInner::getSelectedText() const { - SelectedItems sel = _selected; + auto selected = _selected; if (_mouseAction == MouseAction::Selecting && _dragSelFrom && _dragSelTo) { - applyDragSelection(&sel); + applyDragSelection(&selected); } - if (sel.empty()) { + if (selected.empty()) { return TextWithEntities(); } - if (sel.cbegin()->second != FullSelection) { - return sel.cbegin()->first->selectedText(sel.cbegin()->second); + if (selected.cbegin()->second != FullSelection) { + const auto [item, selection] = *selected.cbegin(); + return item->selectedText(selection); } int fullSize = 0; QString timeFormat(qsl(", [dd.MM.yy hh:mm]\n")); QMap texts; - for (auto &selected : sel) { - auto item = selected.first; + for (const auto [item, selection] : selected) { if (item->detached()) continue; auto time = item->date.toString(timeFormat); @@ -2007,10 +2049,11 @@ MessageIdsList HistoryInner::getSelectedItems() const { return result; } -void HistoryInner::selectItem(HistoryItem *item) { +void HistoryInner::selectItem(not_null item) { if (!_selected.empty() && _selected.cbegin()->second != FullSelection) { _selected.clear(); - } else if (_selected.size() == MaxSelectedItems && _selected.find(item) == _selected.cend()) { + } else if (_selected.size() == MaxSelectedItems + && _selected.find(item) == _selected.cend()) { return; } _selected.emplace(item, FullSelection); @@ -2059,10 +2102,16 @@ void HistoryInner::onUpdateSelected() { HistoryTextState dragState; ClickHandlerHost *lnkhost = nullptr; - bool selectingText = (item == _mouseActionItem && item == App::hoveredItem() && !_selected.empty() && _selected.cbegin()->second != FullSelection); + auto selectingText = (item == _mouseActionItem) + && (item == App::hoveredItem()) + && !_selected.empty() + && (_selected.cbegin()->second != FullSelection); if (point.y() < _historyPaddingTop) { if (_botAbout && !_botAbout->info->text.isEmpty() && _botAbout->height > 0) { - dragState = _botAbout->info->text.getState(point - _botAbout->rect.topLeft() - QPoint(st::msgPadding.left(), st::msgPadding.top() + st::botDescSkip + st::msgNameFont->height), _botAbout->width); + dragState = HistoryTextState(nullptr, _botAbout->info->text.getState( + point - _botAbout->rect.topLeft() - QPoint(st::msgPadding.left(), st::msgPadding.top() + st::botDescSkip + st::msgNameFont->height), + _botAbout->width)); + _dragStateItem = App::histItemById(dragState.itemId); lnkhost = _botAbout.get(); } } else if (item) { @@ -2077,7 +2126,7 @@ void HistoryInner::onUpdateSelected() { auto dateHeight = st::msgServicePadding.bottom() + st::msgServiceFont->height + st::msgServicePadding.top(); auto scrollDateOpacity = _scrollDateOpacity.current(_scrollDateShown ? 1. : 0.); - enumerateDates([this, &dragState, &lnkhost, &point, scrollDateOpacity, dateHeight/*, lastDate, showFloatingBefore*/](not_null item, int itemtop, int dateTop) { + enumerateDates([&](not_null item, int itemtop, int dateTop) { // stop enumeration if the date is above our point if (dateTop + dateHeight <= point.y()) { return false; @@ -2116,7 +2165,10 @@ void HistoryInner::onUpdateSelected() { } else { static_cast(_scrollDateLink.data())->setDate(item->date.date()); } - dragState.link = _scrollDateLink; + dragState = HistoryTextState( + nullptr, + _scrollDateLink); + _dragStateItem = App::histItemById(dragState.itemId); lnkhost = item; } } @@ -2132,11 +2184,12 @@ void HistoryInner::onUpdateSelected() { selectingText = false; } dragState = item->getState(m, request); + _dragStateItem = App::histItemById(dragState.itemId); lnkhost = item; if (!dragState.link && m.x() >= st::historyPhotoLeft && m.x() < st::historyPhotoLeft + st::msgPhotoSize) { if (auto msg = item->toHistoryMessage()) { if (msg->hasFromPhoto()) { - enumerateUserpics([&dragState, &lnkhost, &point](not_null message, int userpicTop) -> bool { + enumerateUserpics([&](not_null message, int userpicTop) -> bool { // stop enumeration if the userpic is below our point if (userpicTop > point.y()) { return false; @@ -2144,7 +2197,10 @@ void HistoryInner::onUpdateSelected() { // stop enumeration if we've found a userpic under the cursor if (point.y() >= userpicTop && point.y() < userpicTop + st::msgPhotoSize) { - dragState.link = message->displayFrom()->openLink(); + dragState = HistoryTextState( + nullptr, + message->displayFrom()->openLink()); + _dragStateItem = App::histItemById(dragState.itemId); lnkhost = message; return false; } @@ -2171,7 +2227,7 @@ void HistoryInner::onUpdateSelected() { } else if (_mouseCursorState == HistoryInTextCursorState && (_selected.empty() || _selected.cbegin()->second != FullSelection)) { cur = style::cur_text; } else if (_mouseCursorState == HistoryInDateCursorState) { - // cur = style::cur_cross; + //cur = style::cur_cross; } } else if (item) { if (_mouseAction == MouseAction::Selecting) { @@ -2235,7 +2291,9 @@ void HistoryInner::onUpdateSelected() { if (ClickHandler::getPressed()) { cur = style::cur_pointer; - } else if (_mouseAction == MouseAction::Selecting && !_selected.empty() && _selected.cbegin()->second != FullSelection) { + } else if ((_mouseAction == MouseAction::Selecting) + && !_selected.empty() + && (_selected.cbegin()->second != FullSelection)) { if (!_dragSelFrom || !_dragSelTo) { cur = style::cur_text; } @@ -2243,7 +2301,7 @@ void HistoryInner::onUpdateSelected() { } // Voice message seek support. - if (auto pressedItem = App::pressedLinkItem()) { + if (const auto pressedItem = _dragStateItem) { if (!pressedItem->detached()) { if (pressedItem->history() == _history || pressedItem->history() == _migrated) { auto adjustedPoint = mapPointToItem(point, pressedItem); @@ -2370,25 +2428,125 @@ void HistoryInner::applyDragSelection() { applyDragSelection(&_selected); } -void HistoryInner::addSelectionRange(SelectedItems *toItems, int32 fromblock, int32 fromitem, int32 toblock, int32 toitem, History *h) const { +bool HistoryInner::isFullSelected( + not_null toItems, + not_null item) const { + const auto group = [&] { + if (const auto group = item->Get()) { + if (group->leader == item) { + return group; + } + return group->leader->Get(); + } + return (HistoryMessageGroup*)nullptr; + }(); + const auto singleSelected = [&](not_null item) { + const auto i = toItems->find(item); + return (i != toItems->cend()) && (i->second == FullSelection); + }; + if (group) { + if (!singleSelected(group->leader)) { + return false; + } + for (const auto other : group->others) { + if (!singleSelected(other)) { + return false; + } + } + } else if (!singleSelected(item)) { + return false; + } + return true; +} + +void HistoryInner::changeDragSelection( + not_null toItems, + not_null item, + SelectAction action) const { + if (action == SelectAction::Invert) { + action = isFullSelected(toItems, item) + ? SelectAction::Deselect + : SelectAction::Select; + } + auto total = toItems->size(); + const auto add = (action == SelectAction::Select); + const auto goodForAdding = [&](not_null item) { + if (item->id <= 0 || item->serviceMsg()) { + return false; + } + if (toItems->find(item) == toItems->end()) { + ++total; + } + return true; + }; + const auto addSingle = [&](not_null item) { + const auto i = toItems->find(item); + if (i == toItems->cend()) { + toItems->emplace(item, FullSelection); + } else if (i->second != FullSelection) { + i->second = FullSelection; + } + }; + const auto removeSingle = [&](not_null item) { + const auto i = toItems->find(item); + if (i != toItems->cend()) { + toItems->erase(i); + } + }; + const auto group = [&] { + if (const auto group = item->Get()) { + if (group->leader == item) { + return group; + } + return group->leader->Get(); + } + return (HistoryMessageGroup*)nullptr; + }(); + if (group) { + const auto adding = [&] { + if (!add || !goodForAdding(group->leader)) { + return false; + } + for (const auto other : group->others) { + if (!goodForAdding(other)) { + return false; + } + } + return (total <= MaxSelectedItems); + }(); + if (adding) { + addSingle(group->leader); + for (const auto other : group->others) { + addSingle(other); + } + } else { + removeSingle(group->leader); + for (const auto other : group->others) { + removeSingle(other); + } + } + } else { + if (add && goodForAdding(item) && total <= MaxSelectedItems) { + addSingle(item); + } else { + removeSingle(item); + } + } +} + +void HistoryInner::addSelectionRange( + not_null toItems, + not_null history, + int fromblock, + int fromitem, + int toblock, + int toitem) const { if (fromblock >= 0 && fromitem >= 0 && toblock >= 0 && toitem >= 0) { for (; fromblock <= toblock; ++fromblock) { - auto block = h->blocks[fromblock]; - for (int32 cnt = (fromblock < toblock) ? block->items.size() : (toitem + 1); fromitem < cnt; ++fromitem) { + auto block = history->blocks[fromblock]; + for (int cnt = (fromblock < toblock) ? block->items.size() : (toitem + 1); fromitem < cnt; ++fromitem) { auto item = block->items[fromitem]; - auto i = toItems->find(item); - if (item->id > 0 && !item->serviceMsg()) { - if (i == toItems->cend()) { - if (toItems->size() >= MaxSelectedItems) break; - toItems->emplace(item, FullSelection); - } else if (i->second != FullSelection) { - i->second = FullSelection; - } - } else { - if (i != toItems->cend()) { - toItems->erase(i); - } - } + changeDragSelection(toItems, item, SelectAction::Select); } if (toItems->size() >= MaxSelectedItems) break; fromitem = 0; @@ -2396,27 +2554,33 @@ void HistoryInner::addSelectionRange(SelectedItems *toItems, int32 fromblock, in } } -void HistoryInner::applyDragSelection(SelectedItems *toItems) const { - int32 selfromy = itemTop(_dragSelFrom), seltoy = itemTop(_dragSelTo); +void HistoryInner::applyDragSelection( + not_null toItems) const { + const auto selfromy = itemTop(_dragSelFrom); + const auto seltoy = [&] { + auto result = itemTop(_dragSelTo); + return (result < 0) ? result : (result + _dragSelTo->height()); + }(); if (selfromy < 0 || seltoy < 0) { return; } - seltoy += _dragSelTo->height(); if (!toItems->empty() && toItems->cbegin()->second != FullSelection) { toItems->clear(); } if (_dragSelecting) { - int32 fromblock = _dragSelFrom->block()->indexInHistory(), fromitem = _dragSelFrom->indexInBlock(); - int32 toblock = _dragSelTo->block()->indexInHistory(), toitem = _dragSelTo->indexInBlock(); + auto fromblock = _dragSelFrom->block()->indexInHistory(); + auto fromitem = _dragSelFrom->indexInBlock(); + auto toblock = _dragSelTo->block()->indexInHistory(); + auto toitem = _dragSelTo->indexInBlock(); if (_migrated) { if (_dragSelFrom->history() == _migrated) { if (_dragSelTo->history() == _migrated) { - addSelectionRange(toItems, fromblock, fromitem, toblock, toitem, _migrated); + addSelectionRange(toItems, _migrated, fromblock, fromitem, toblock, toitem); toblock = -1; toitem = -1; } else { - addSelectionRange(toItems, fromblock, fromitem, _migrated->blocks.size() - 1, _migrated->blocks.back()->items.size() - 1, _migrated); + addSelectionRange(toItems, _migrated, fromblock, fromitem, _migrated->blocks.size() - 1, _migrated->blocks.back()->items.size() - 1); } fromblock = 0; fromitem = 0; @@ -2425,20 +2589,20 @@ void HistoryInner::applyDragSelection(SelectedItems *toItems) const { toitem = -1; } } - addSelectionRange(toItems, fromblock, fromitem, toblock, toitem, _history); + addSelectionRange(toItems, _history, fromblock, fromitem, toblock, toitem); } else { - for (auto i = toItems->begin(); i != toItems->cend();) { + auto toRemove = std::vector>(); + for (auto i = toItems->begin(); i != toItems->cend(); ++i) { auto iy = itemTop(i->first); - if (iy < 0) { - if (iy < -1) i = toItems->erase(i); - continue; - } - if (iy >= selfromy && iy < seltoy) { - i = toItems->erase(i); - } else { - ++i; + if (iy < -1) { + toRemove.push_back(i->first); + } else if (iy >= 0 && iy >= selfromy && iy < seltoy) { + toRemove.push_back(i->first); } } + for (const auto item : toRemove) { + changeDragSelection(toItems, item, SelectAction::Deselect); + } } } diff --git a/Telegram/SourceFiles/history/history_inner_widget.h b/Telegram/SourceFiles/history/history_inner_widget.h index 880a8f3362..92c9e0dec8 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.h +++ b/Telegram/SourceFiles/history/history_inner_widget.h @@ -66,7 +66,7 @@ public: HistoryTopBarWidget::SelectedState getSelectionState() const; void clearSelectedItems(bool onlyTextSelection = false); MessageIdsList getSelectedItems() const; - void selectItem(HistoryItem *item); + void selectItem(not_null item); void updateBotInfo(bool recount = true); @@ -165,6 +165,10 @@ private: HistoryItem *prevItem(HistoryItem *item); HistoryItem *nextItem(HistoryItem *item); void updateDragSelection(HistoryItem *dragSelFrom, HistoryItem *dragSelTo, bool dragSelecting); + TextSelection itemRenderSelection( + not_null item, + int selfromy, + int seltoy) const; void setToClipboard(const TextWithEntities &forClipboard, QClipboard::Mode mode = QClipboard::Clipboard); @@ -217,8 +221,26 @@ private: using SelectedItems = std::map>; SelectedItems _selected; void applyDragSelection(); - void applyDragSelection(SelectedItems *toItems) const; - void addSelectionRange(SelectedItems *toItems, int32 fromblock, int32 fromitem, int32 toblock, int32 toitem, History *h) const; + void applyDragSelection(not_null toItems) const; + void addSelectionRange( + not_null toItems, + not_null history, + int fromblock, + int fromitem, + int toblock, + int toitem) const; + bool isFullSelected( + not_null toItems, + not_null item) const; + enum class SelectAction { + Select, + Deselect, + Invert, + }; + void changeDragSelection( + not_null toItems, + not_null item, + SelectAction action) const; // Does any of the shown histories has this flag set. bool hasPendingResizedItems() const { @@ -230,6 +252,7 @@ private: QPoint _dragStartPosition; QPoint _mousePosition; HistoryItem *_mouseActionItem = nullptr; + HistoryItem *_dragStateItem = nullptr; HistoryCursorState _mouseCursorState = HistoryDefaultCursorState; uint16 _mouseTextSymbol = 0; bool _pressWasInactive = false; diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 7e710ca5d1..b1ad869925 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -46,6 +46,29 @@ constexpr int kAttachMessageToPreviousSecondsDelta = 900; } // namespace +HistoryTextState::HistoryTextState(not_null item) +: itemId(item->fullId()) { +} + +HistoryTextState::HistoryTextState( + not_null item, + const Text::StateResult &state) +: itemId(item->fullId()) +, cursor(state.uponSymbol + ? HistoryInTextCursorState + : HistoryDefaultCursorState) +, link(state.link) +, afterSymbol(state.afterSymbol) +, symbol(state.symbol) { +} + +HistoryTextState::HistoryTextState( + not_null item, + ClickHandlerPtr link) +: itemId(item->fullId()) +, link(link) { +} + ReplyMarkupClickHandler::ReplyMarkupClickHandler(const HistoryItem *item, int row, int col) : _itemId(item->fullId()) , _row(row) diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h index 5cbb5c67ce..02206086fe 100644 --- a/Telegram/SourceFiles/history/history_item.h +++ b/Telegram/SourceFiles/history/history_item.h @@ -79,25 +79,33 @@ enum HistoryCursorState { struct HistoryTextState { HistoryTextState() = default; - HistoryTextState(const Text::StateResult &state) - : cursor(state.uponSymbol ? HistoryInTextCursorState : HistoryDefaultCursorState) - , link(state.link) - , afterSymbol(state.afterSymbol) - , symbol(state.symbol) { + HistoryTextState(not_null item); + HistoryTextState( + not_null item, + const Text::StateResult &state); + HistoryTextState( + not_null item, + ClickHandlerPtr link); + HistoryTextState( + std::nullptr_t, + const Text::StateResult &state) + : cursor(state.uponSymbol + ? HistoryInTextCursorState + : HistoryDefaultCursorState) + , link(state.link) + , afterSymbol(state.afterSymbol) + , symbol(state.symbol) { } - HistoryTextState &operator=(const Text::StateResult &state) { - cursor = state.uponSymbol ? HistoryInTextCursorState : HistoryDefaultCursorState; - link = state.link; - afterSymbol = state.afterSymbol; - symbol = state.symbol; - return *this; - } - HistoryTextState(ClickHandlerPtr link) : link(link) { + HistoryTextState(std::nullptr_t, ClickHandlerPtr link) + : link(link) { } + + FullMsgId itemId; HistoryCursorState cursor = HistoryDefaultCursorState; ClickHandlerPtr link; bool afterSymbol = false; uint16 symbol = 0; + }; struct HistoryStateRequest { diff --git a/Telegram/SourceFiles/history/history_media.h b/Telegram/SourceFiles/history/history_media.h index a5554992fa..f0664871da 100644 --- a/Telegram/SourceFiles/history/history_media.h +++ b/Telegram/SourceFiles/history/history_media.h @@ -135,7 +135,8 @@ public: return false; } virtual std::unique_ptr clone( - not_null newParent) const = 0; + not_null newParent, + not_null realParent) const = 0; virtual DocumentData *getDocument() { return nullptr; diff --git a/Telegram/SourceFiles/history/history_media_grouped.cpp b/Telegram/SourceFiles/history/history_media_grouped.cpp index 831ac3fc7f..e48b253134 100644 --- a/Telegram/SourceFiles/history/history_media_grouped.cpp +++ b/Telegram/SourceFiles/history/history_media_grouped.cpp @@ -54,13 +54,20 @@ HistoryGroupedMedia::Element::Element(not_null item) HistoryGroupedMedia::HistoryGroupedMedia( not_null parent, const std::vector> &others) -: HistoryMedia(parent) { +: HistoryMedia(parent) +, _caption(st::minPhotoSize - st::msgPadding.left() - st::msgPadding.right()) { const auto result = applyGroup(others); Ensures(result); } void HistoryGroupedMedia::initDimensions() { + if (_caption.hasSkipBlock()) { + _caption.setSkipBlock( + _parent->skipBlockWidth(), + _parent->skipBlockHeight()); + } + std::vector sizes; sizes.reserve(_elements.size()); for (const auto &element : _elements) { @@ -84,6 +91,14 @@ void HistoryGroupedMedia::initDimensions() { _elements[i].initialGeometry = item.geometry; _elements[i].sides = item.sides; } + + if (!_caption.isEmpty()) { + auto captionw = _maxw - st::msgPadding.left() - st::msgPadding.right(); + _minh += st::mediaCaptionSkip + _caption.countHeight(captionw); + if (isBubbleBottom()) { + _minh += st::msgPadding.bottom(); + } + } } int HistoryGroupedMedia::resizeGetHeight(int width) { @@ -94,7 +109,7 @@ int HistoryGroupedMedia::resizeGetHeight(int width) { } const auto initialSpacing = st::historyGroupSkip; - const auto factor = width / float64(st::historyGroupWidthMax); + const auto factor = width / float64(_maxw); const auto scale = [&](int value) { return int(std::round(value * factor)); }; @@ -124,6 +139,15 @@ int HistoryGroupedMedia::resizeGetHeight(int width) { accumulate_max(_height, top + height); } + + if (!_caption.isEmpty()) { + const auto captionw = _width - st::msgPadding.left() - st::msgPadding.right(); + _height += st::mediaPadding.bottom() + st::mediaCaptionSkip + _caption.countHeight(captionw); + if (isBubbleBottom()) { + _height += st::msgPadding.bottom(); + } + } + return _height; } @@ -132,7 +156,13 @@ void HistoryGroupedMedia::draw( const QRect &clip, TextSelection selection, TimeMs ms) const { - for (const auto &element : _elements) { + for (auto i = 0, count = int(_elements.size()); i != count; ++i) { + const auto &element = _elements[i]; + const auto elementSelection = (selection == FullSelection) + ? FullSelection + : IsGroupItemSelection(selection, i) + ? FullSelection + : TextSelection(); auto corners = GetCornersFromSides(element.sides); if (!isBubbleTop()) { corners &= ~(RectPart::TopLeft | RectPart::TopRight); @@ -143,13 +173,36 @@ void HistoryGroupedMedia::draw( element.content->drawGrouped( p, clip, - selection, + elementSelection, ms, element.geometry, corners, &element.cacheKey, &element.cache); } + + // date + const auto selected = (selection == FullSelection); + if (!_caption.isEmpty()) { + const auto captionw = _width - st::msgPadding.left() - st::msgPadding.right(); + const auto outbg = _parent->hasOutLayout(); + const auto captiony = _height + - (isBubbleBottom() ? st::msgPadding.bottom() : 0) + - _caption.countHeight(captionw); + p.setPen(outbg ? (selected ? st::historyTextOutFgSelected : st::historyTextOutFg) : (selected ? st::historyTextInFgSelected : st::historyTextInFg)); + _caption.draw(p, st::msgPadding.left(), captiony, captionw, style::al_left, 0, -1, selection); + } else if (_parent->getMedia() == this) { + auto fullRight = _width; + auto fullBottom = _height; + if (_parent->id < 0 || App::hoveredItem() == _parent) { + _parent->drawInfo(p, fullRight, fullBottom, _width, selected, InfoDisplayOverImage); + } + if (!_parent->hasBubble() && _parent->displayRightAction()) { + auto fastShareLeft = (fullRight + st::historyFastShareLeft); + auto fastShareTop = (fullBottom - st::historyFastShareBottom - st::historyFastShareSize); + _parent->drawRightAction(p, fastShareLeft, fastShareTop, _width); + } + } } HistoryTextState HistoryGroupedMedia::getState( @@ -157,10 +210,39 @@ HistoryTextState HistoryGroupedMedia::getState( HistoryStateRequest request) const { for (const auto &element : _elements) { if (element.geometry.contains(point)) { - return element.content->getStateGrouped( + auto result = element.content->getStateGrouped( element.geometry, point, request); + result.itemId = element.item->fullId(); + return result; + } + } + if (!_caption.isEmpty()) { + const auto captionw = _width - st::msgPadding.left() - st::msgPadding.right(); + const auto captiony = _height + - (isBubbleBottom() ? st::msgPadding.bottom() : 0) + - _caption.countHeight(captionw); + if (QRect(st::msgPadding.left(), captiony, captionw, _height - captiony).contains(point)) { + return HistoryTextState(_parent, _caption.getState( + point - QPoint(st::msgPadding.left(), captiony), + captionw, + request.forText())); + } + } + auto result = HistoryTextState(_parent); + if (_caption.isEmpty() && _parent->getMedia() == this) { + auto fullRight = _width; + auto fullBottom = _height; + if (_parent->pointInTime(fullRight, fullBottom, point, InfoDisplayOverImage)) { + result.cursor = HistoryInDateCursorState; + } + if (!_parent->hasBubble() && _parent->displayRightAction()) { + auto fastShareLeft = (fullRight + st::historyFastShareLeft); + auto fastShareTop = (fullBottom - st::historyFastShareBottom - st::historyFastShareSize); + if (QRect(fastShareLeft, fastShareTop, st::historyFastShareSize, st::historyFastShareSize).contains(point)) { + result.link = _parent->rightActionLink(); + } } } return HistoryTextState(); @@ -191,6 +273,14 @@ TextSelection HistoryGroupedMedia::adjustSelection( return _caption.adjustSelection(selection, type); } +QString HistoryGroupedMedia::notificationText() const { + return WithCaptionNotificationText(lang(lng_in_dlg_album), _caption); +} + +QString HistoryGroupedMedia::inDialogsText() const { + return WithCaptionDialogsText(lang(lng_in_dlg_album), _caption); +} + TextWithEntities HistoryGroupedMedia::selectedText( TextSelection selection) const { return WithCaptionSelectedText( @@ -212,6 +302,9 @@ void HistoryGroupedMedia::clickHandlerPressedChanged( bool pressed) { for (const auto &element : _elements) { element.content->clickHandlerPressedChanged(p, pressed); + if (pressed && element.content->dragItemByHandler(p)) { + App::pressedLinkItem(element.item); + } } } @@ -243,7 +336,7 @@ bool HistoryGroupedMedia::applyGroup( Assert(media != nullptr && media->canBeGrouped()); _elements.push_back(Element(item)); - _elements.back().content = item->getMedia()->clone(_parent); + _elements.back().content = item->getMedia()->clone(_parent, item); }; if (_elements.empty()) { pushElement(_parent); diff --git a/Telegram/SourceFiles/history/history_media_grouped.h b/Telegram/SourceFiles/history/history_media_grouped.h index f1025d78f5..06071bd63f 100644 --- a/Telegram/SourceFiles/history/history_media_grouped.h +++ b/Telegram/SourceFiles/history/history_media_grouped.h @@ -34,7 +34,8 @@ public: return MediaTypeGrouped; } std::unique_ptr clone( - not_null newParent) const override { + not_null newParent, + not_null realParent) const override { Unexpected("Clone HistoryGroupedMedia."); } @@ -64,6 +65,8 @@ public: return !_caption.isEmpty(); } + QString notificationText() const override; + QString inDialogsText() const override; TextWithEntities selectedText(TextSelection selection) const override; void clickHandlerActiveChanged( diff --git a/Telegram/SourceFiles/history/history_media_types.cpp b/Telegram/SourceFiles/history/history_media_types.cpp index 56bfcf2b2e..6faa7b8732 100644 --- a/Telegram/SourceFiles/history/history_media_types.cpp +++ b/Telegram/SourceFiles/history/history_media_types.cpp @@ -272,12 +272,16 @@ HistoryPhoto::HistoryPhoto( : HistoryFileMedia(parent) , _data(photo) , _caption(st::minPhotoSize - st::msgPadding.left() - st::msgPadding.right()) { + const auto fullId = parent->fullId(); setLinks( - MakeShared(_data), - MakeShared(_data), - MakeShared(_data)); + MakeShared(_data, fullId), + MakeShared(_data, fullId), + MakeShared(_data, fullId)); if (!caption.isEmpty()) { - _caption.setText(st::messageTextStyle, caption + _parent->skipBlock(), itemTextNoMonoOptions(_parent)); + _caption.setText( + st::messageTextStyle, + caption + _parent->skipBlock(), + itemTextNoMonoOptions(_parent)); } init(); } @@ -289,10 +293,11 @@ HistoryPhoto::HistoryPhoto( int width) : HistoryFileMedia(parent) , _data(photo) { + const auto fullId = parent->fullId(); setLinks( - MakeShared(_data, chat), - MakeShared(_data, chat), - MakeShared(_data, chat)); + MakeShared(_data, fullId, chat), + MakeShared(_data, fullId, chat), + MakeShared(_data, fullId, chat)); _width = width; init(); @@ -308,16 +313,18 @@ HistoryPhoto::HistoryPhoto( HistoryPhoto::HistoryPhoto( not_null parent, + not_null realParent, const HistoryPhoto &other) : HistoryFileMedia(parent) , _data(other._data) , _pixw(other._pixw) , _pixh(other._pixh) , _caption(other._caption) { + const auto fullId = realParent->fullId(); setLinks( - MakeShared(_data), - MakeShared(_data), - MakeShared(_data)); + MakeShared(_data, fullId), + MakeShared(_data, fullId), + MakeShared(_data, fullId)); init(); } @@ -533,9 +540,11 @@ void HistoryPhoto::draw(Painter &p, const QRect &r, TextSelection selection, Tim } HistoryTextState HistoryPhoto::getState(QPoint point, HistoryStateRequest request) const { - HistoryTextState result; + auto result = HistoryTextState(_parent); - if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return result; + if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) { + return result; + } int skipx = 0, skipy = 0, width = _width, height = _height; auto bubble = _parent->hasBubble(); @@ -549,7 +558,10 @@ HistoryTextState HistoryPhoto::getState(QPoint point, HistoryStateRequest reques height -= st::msgPadding.bottom(); } if (QRect(st::msgPadding.left(), height, captionw, _height - height).contains(point)) { - result = _caption.getState(point - QPoint(st::msgPadding.left(), height), captionw, request.forText()); + result = HistoryTextState(_parent, _caption.getState( + point - QPoint(st::msgPadding.left(), height), + captionw, + request.forText())); return result; } height -= st::mediaCaptionSkip; @@ -696,7 +708,7 @@ HistoryTextState HistoryPhoto::getStateGrouped( return {}; } const auto delayed = _data->full->toDelayedStorageImage(); - return _data->uploading() + return HistoryTextState(_parent, _data->uploading() ? _cancell : _data->loaded() ? _openl @@ -704,7 +716,7 @@ HistoryTextState HistoryPhoto::getStateGrouped( ? ((!delayed || !delayed->location().isNull()) ? _cancell : ClickHandlerPtr()) - : _savel; + : _savel); } void HistoryPhoto::validateGroupedCache( @@ -872,10 +884,13 @@ HistoryVideo::HistoryVideo( , _thumbw(1) , _caption(st::minPhotoSize - st::msgPadding.left() - st::msgPadding.right()) { if (!caption.isEmpty()) { - _caption.setText(st::messageTextStyle, caption + _parent->skipBlock(), itemTextNoMonoOptions(_parent)); + _caption.setText( + st::messageTextStyle, + caption + _parent->skipBlock(), + itemTextNoMonoOptions(_parent)); } - setDocumentLinks(_data); + setDocumentLinks(_data, parent); setStatusSize(FileStatusSizeReady); @@ -884,12 +899,13 @@ HistoryVideo::HistoryVideo( HistoryVideo::HistoryVideo( not_null parent, + not_null realParent, const HistoryVideo &other) : HistoryFileMedia(parent) , _data(other._data) , _thumbw(other._thumbw) , _caption(other._caption) { - setDocumentLinks(_data); + setDocumentLinks(_data, realParent); setStatusSize(other._statusSize); } @@ -1089,7 +1105,7 @@ HistoryTextState HistoryVideo::getState(QPoint point, HistoryStateRequest reques return {}; } - HistoryTextState result; + auto result = HistoryTextState(_parent); bool loaded = _data->loaded(); int32 skipx = 0, skipy = 0, width = _width, height = _height; @@ -1105,7 +1121,10 @@ HistoryTextState HistoryVideo::getState(QPoint point, HistoryStateRequest reques height -= st::msgPadding.bottom(); } if (QRect(st::msgPadding.left(), height, captionw, _height - height).contains(point)) { - result = _caption.getState(point - QPoint(st::msgPadding.left(), height), captionw, request.forText()); + result = HistoryTextState(_parent, _caption.getState( + point - QPoint(st::msgPadding.left(), height), + captionw, + request.forText())); } height -= st::mediaCaptionSkip; } @@ -1242,13 +1261,13 @@ HistoryTextState HistoryVideo::getStateGrouped( if (!geometry.contains(point)) { return {}; } - return _data->uploading() + return HistoryTextState(_parent, _data->uploading() ? _cancell : _data->loaded() ? _openl : _data->loading() ? _cancell - : _savel; + : _savel); } void HistoryVideo::validateGroupedCache( @@ -1425,7 +1444,7 @@ HistoryDocument::HistoryDocument( fillNamedFromData(named); } - setDocumentLinks(_data); + setDocumentLinks(_data, parent); setStatusSize(FileStatusSizeReady); @@ -1450,7 +1469,7 @@ HistoryDocument::HistoryDocument( } } - setDocumentLinks(_data); + setDocumentLinks(_data, parent); setStatusSize(other._statusSize); @@ -1821,9 +1840,11 @@ void HistoryDocument::draw(Painter &p, const QRect &r, TextSelection selection, } HistoryTextState HistoryDocument::getState(QPoint point, HistoryStateRequest request) const { - HistoryTextState result; + auto result = HistoryTextState(_parent); - if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return result; + if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) { + return result; + } bool loaded = _data->loaded(); @@ -1882,7 +1903,10 @@ HistoryTextState HistoryDocument::getState(QPoint point, HistoryStateRequest req auto height = _height; if (auto captioned = Get()) { if (point.y() >= bottom) { - result = captioned->_caption.getState(point - QPoint(st::msgPadding.left(), bottom), _width - st::msgPadding.left() - st::msgPadding.right(), request.forText()); + result = HistoryTextState(_parent, captioned->_caption.getState( + point - QPoint(st::msgPadding.left(), bottom), + _width - st::msgPadding.left() - st::msgPadding.right(), + request.forText())); return result; } auto captionw = _width - st::msgPadding.left() - st::msgPadding.right(); @@ -2163,7 +2187,7 @@ HistoryGif::HistoryGif( : HistoryFileMedia(parent) , _data(document) , _caption(st::minPhotoSize - st::msgPadding.left() - st::msgPadding.right()) { - setDocumentLinks(_data, true); + setDocumentLinks(_data, parent, true); setStatusSize(FileStatusSizeReady); @@ -2182,7 +2206,7 @@ HistoryGif::HistoryGif( , _thumbw(other._thumbw) , _thumbh(other._thumbh) , _caption(other._caption) { - setDocumentLinks(_data, true); + setDocumentLinks(_data, parent, true); setStatusSize(other._statusSize); } @@ -2614,9 +2638,11 @@ void HistoryGif::draw(Painter &p, const QRect &r, TextSelection selection, TimeM } HistoryTextState HistoryGif::getState(QPoint point, HistoryStateRequest request) const { - HistoryTextState result; + auto result = HistoryTextState(_parent); - if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return result; + if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) { + return result; + } int32 skipx = 0, skipy = 0, width = _width, height = _height; bool bubble = _parent->hasBubble(); @@ -2630,7 +2656,10 @@ HistoryTextState HistoryGif::getState(QPoint point, HistoryStateRequest request) height -= st::msgPadding.bottom(); } if (QRect(st::msgPadding.left(), height, captionw, _height - height).contains(point)) { - result = _caption.getState(point - QPoint(st::msgPadding.left(), height), captionw, request.forText()); + result = HistoryTextState(_parent, _caption.getState( + point - QPoint(st::msgPadding.left(), height), + captionw, + request.forText())); return result; } height -= st::mediaCaptionSkip; @@ -2679,7 +2708,10 @@ HistoryTextState HistoryGif::getState(QPoint point, HistoryStateRequest request) if (breakEverywhere) { textRequest.flags |= Text::StateRequest::Flag::BreakEverywhere; } - result = forwarded->_text.getState(point - QPoint(rectx + st::msgReplyPadding.left(), recty + st::msgReplyPadding.top()), innerw, textRequest); + result = HistoryTextState(_parent, forwarded->_text.getState( + point - QPoint(rectx + st::msgReplyPadding.left(), recty + st::msgReplyPadding.top()), + innerw, + textRequest)); result.symbol = 0; result.afterSymbol = false; if (breakEverywhere) { @@ -3138,8 +3170,10 @@ void HistorySticker::draw(Painter &p, const QRect &r, TextSelection selection, T } HistoryTextState HistorySticker::getState(QPoint point, HistoryStateRequest request) const { - HistoryTextState result; - if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return result; + auto result = HistoryTextState(_parent); + if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) { + return result; + } auto outbg = _parent->hasOutLayout(); auto childmedia = (_parent->getMedia() != this); @@ -3408,7 +3442,7 @@ void HistoryContact::draw(Painter &p, const QRect &r, TextSelection selection, T } HistoryTextState HistoryContact::getState(QPoint point, HistoryStateRequest request) const { - HistoryTextState result; + auto result = HistoryTextState(_parent); int32 nameleft = 0, nametop = 0, nameright = 0, statustop = 0, linktop = 0; auto topMinus = isBubbleTop() ? 0 : st::msgFileTopMinus; @@ -3556,7 +3590,7 @@ void HistoryCall::draw(Painter &p, const QRect &r, TextSelection selection, Time } HistoryTextState HistoryCall::getState(QPoint point, HistoryStateRequest request) const { - HistoryTextState result; + auto result = HistoryTextState(_parent); if (QRect(0, 0, _width, _height).contains(point)) { result.link = _link; return result; @@ -3617,9 +3651,12 @@ HistoryWebPage::HistoryWebPage(not_null parent, not_null parent, const HistoryWebPage &other) : HistoryMedia(parent) +HistoryWebPage::HistoryWebPage( + not_null parent, + const HistoryWebPage &other) +: HistoryMedia(parent) , _data(other._data) -, _attach(other._attach ? other._attach->clone(parent) : nullptr) +, _attach(other._attach ? other._attach->clone(parent, parent) : nullptr) , _asArticle(other._asArticle) , _title(other._title) , _description(other._description) @@ -3999,9 +4036,11 @@ void HistoryWebPage::draw(Painter &p, const QRect &r, TextSelection selection, T } HistoryTextState HistoryWebPage::getState(QPoint point, HistoryStateRequest request) const { - HistoryTextState result; + auto result = HistoryTextState(_parent); - if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return result; + if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) { + return result; + } int32 skipx = 0, skipy = 0, width = _width, height = _height; QMargins bubble(_attach ? _attach->bubbleMargins() : QMargins()); @@ -4030,7 +4069,11 @@ HistoryTextState HistoryWebPage::getState(QPoint point, HistoryStateRequest requ if (point.y() >= tshift && point.y() < tshift + _titleLines * lineHeight) { Text::StateRequestElided titleRequest = request.forText(); titleRequest.lines = _titleLines; - result = _title.getStateElidedLeft(point - QPoint(padding.left(), tshift), width, _width, titleRequest); + result = HistoryTextState(_parent, _title.getStateElidedLeft( + point - QPoint(padding.left(), tshift), + width, + _width, + titleRequest)); } else if (point.y() >= tshift + _titleLines * lineHeight) { symbolAdd += _title.length(); } @@ -4042,9 +4085,17 @@ HistoryTextState HistoryWebPage::getState(QPoint point, HistoryStateRequest requ if (_descriptionLines > 0) { Text::StateRequestElided descriptionRequest = request.forText(); descriptionRequest.lines = _descriptionLines; - result = _description.getStateElidedLeft(point - QPoint(padding.left(), tshift), width, _width, descriptionRequest); + result = HistoryTextState(_parent, _description.getStateElidedLeft( + point - QPoint(padding.left(), tshift), + width, + _width, + descriptionRequest)); } else { - result = _description.getStateLeft(point - QPoint(padding.left(), tshift), width, _width, request.forText()); + result = HistoryTextState(_parent, _description.getStateLeft( + point - QPoint(padding.left(), tshift), + width, + _width, + request.forText())); } } else if (point.y() >= tshift + descriptionHeight) { symbolAdd += _description.length(); @@ -4178,7 +4229,7 @@ HistoryGame::HistoryGame( const HistoryGame &other) : HistoryMedia(parent) , _data(other._data) -, _attach(other._attach ? other._attach->clone(parent) : nullptr) +, _attach(other._attach ? other._attach->clone(parent, parent) : nullptr) , _title(other._title) , _description(other._description) { } @@ -4395,9 +4446,11 @@ void HistoryGame::draw(Painter &p, const QRect &r, TextSelection selection, Time } HistoryTextState HistoryGame::getState(QPoint point, HistoryStateRequest request) const { - HistoryTextState result; + auto result = HistoryTextState(_parent); - if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return result; + if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) { + return result; + } int32 width = _width, height = _height; QMargins bubble(_attach ? _attach->bubbleMargins() : QMargins()); @@ -4416,7 +4469,11 @@ HistoryTextState HistoryGame::getState(QPoint point, HistoryStateRequest request if (point.y() >= tshift && point.y() < tshift + _titleLines * lineHeight) { Text::StateRequestElided titleRequest = request.forText(); titleRequest.lines = _titleLines; - result = _title.getStateElidedLeft(point - QPoint(padding.left(), tshift), width, _width, titleRequest); + result = HistoryTextState(_parent, _title.getStateElidedLeft( + point - QPoint(padding.left(), tshift), + width, + _width, + titleRequest)); } else if (point.y() >= tshift + _titleLines * lineHeight) { symbolAdd += _title.length(); } @@ -4426,7 +4483,11 @@ HistoryTextState HistoryGame::getState(QPoint point, HistoryStateRequest request if (point.y() >= tshift && point.y() < tshift + _descriptionLines * lineHeight) { Text::StateRequestElided descriptionRequest = request.forText(); descriptionRequest.lines = _descriptionLines; - result = _description.getStateElidedLeft(point - QPoint(padding.left(), tshift), width, _width, descriptionRequest); + result = HistoryTextState(_parent, _description.getStateElidedLeft( + point - QPoint(padding.left(), tshift), + width, + _width, + descriptionRequest)); } else if (point.y() >= tshift + _descriptionLines * lineHeight) { symbolAdd += _description.length(); } @@ -4576,7 +4637,7 @@ HistoryInvoice::HistoryInvoice( not_null parent, const HistoryInvoice &other) : HistoryMedia(parent) -, _attach(other._attach ? other._attach->clone(parent) : nullptr) +, _attach(other._attach ? other._attach->clone(parent, parent) : nullptr) , _titleHeight(other._titleHeight) , _descriptionHeight(other._descriptionHeight) , _title(other._title) @@ -4833,9 +4894,11 @@ void HistoryInvoice::draw(Painter &p, const QRect &r, TextSelection selection, T } HistoryTextState HistoryInvoice::getState(QPoint point, HistoryStateRequest request) const { - HistoryTextState result; + auto result = HistoryTextState(_parent); - if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return result; + if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) { + return result; + } int32 width = _width, height = _height; QMargins bubble(_attach ? _attach->bubbleMargins() : QMargins()); @@ -4853,7 +4916,11 @@ HistoryTextState HistoryInvoice::getState(QPoint point, HistoryStateRequest requ if (point.y() >= tshift && point.y() < tshift + _titleHeight) { Text::StateRequestElided titleRequest = request.forText(); titleRequest.lines = _titleHeight / lineHeight; - result = _title.getStateElidedLeft(point - QPoint(padding.left(), tshift), width, _width, titleRequest); + result = HistoryTextState(_parent, _title.getStateElidedLeft( + point - QPoint(padding.left(), tshift), + width, + _width, + titleRequest)); } else if (point.y() >= tshift + _titleHeight) { symbolAdd += _title.length(); } @@ -4861,7 +4928,11 @@ HistoryTextState HistoryInvoice::getState(QPoint point, HistoryStateRequest requ } if (_descriptionHeight) { if (point.y() >= tshift && point.y() < tshift + _descriptionHeight) { - result = _description.getStateLeft(point - QPoint(padding.left(), tshift), width, _width, request.forText()); + result = HistoryTextState(_parent, _description.getStateLeft( + point - QPoint(padding.left(), tshift), + width, + _width, + request.forText())); } else if (point.y() >= tshift + _descriptionHeight) { symbolAdd += _description.length(); } @@ -5122,10 +5193,12 @@ void HistoryLocation::draw(Painter &p, const QRect &r, TextSelection selection, } HistoryTextState HistoryLocation::getState(QPoint point, HistoryStateRequest request) const { - HistoryTextState result; + auto result = HistoryTextState(_parent); auto symbolAdd = 0; - if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return result; + if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) { + return result; + } int32 skipx = 0, skipy = 0, width = _width, height = _height; bool bubble = _parent->hasBubble(); @@ -5145,7 +5218,11 @@ HistoryTextState HistoryLocation::getState(QPoint point, HistoryStateRequest req if (!_title.isEmpty()) { auto titleh = qMin(_title.countHeight(textw), 2 * st::webPageTitleFont->height); if (point.y() >= skipy && point.y() < skipy + titleh) { - result = _title.getStateLeft(point - QPoint(skipx + st::msgPadding.left(), skipy), textw, _width, request.forText()); + result = HistoryTextState(_parent, _title.getStateLeft( + point - QPoint(skipx + st::msgPadding.left(), skipy), + textw, + _width, + request.forText())); return result; } else if (point.y() >= skipy + titleh) { symbolAdd += _title.length(); @@ -5155,7 +5232,11 @@ HistoryTextState HistoryLocation::getState(QPoint point, HistoryStateRequest req if (!_description.isEmpty()) { auto descriptionh = qMin(_description.countHeight(textw), 3 * st::webPageDescriptionFont->height); if (point.y() >= skipy && point.y() < skipy + descriptionh) { - result = _description.getStateLeft(point - QPoint(skipx + st::msgPadding.left(), skipy), textw, _width, request.forText()); + result = HistoryTextState(_parent, _description.getStateLeft( + point - QPoint(skipx + st::msgPadding.left(), skipy), + textw, + _width, + request.forText())); } else if (point.y() >= skipy + descriptionh) { symbolAdd += _description.length(); } diff --git a/Telegram/SourceFiles/history/history_media_types.h b/Telegram/SourceFiles/history/history_media_types.h index e6f0785731..54386cfed2 100644 --- a/Telegram/SourceFiles/history/history_media_types.h +++ b/Telegram/SourceFiles/history/history_media_types.h @@ -72,21 +72,28 @@ public: protected: ClickHandlerPtr _openl, _savel, _cancell; void setLinks(ClickHandlerPtr &&openl, ClickHandlerPtr &&savel, ClickHandlerPtr &&cancell); - void setDocumentLinks(DocumentData *document, bool inlinegif = false) { + void setDocumentLinks( + not_null document, + not_null realParent, + bool inlinegif = false) { ClickHandlerPtr open, save; + const auto context = realParent->fullId(); if (inlinegif) { - open = MakeShared(document); + open = MakeShared(document, context); } else { - open = MakeShared(document); + open = MakeShared(document, context); } if (inlinegif) { - save = MakeShared(document); + save = MakeShared(document, context); } else if (document->isVoiceMessage()) { - save = MakeShared(document); + save = MakeShared(document, context); } else { - save = MakeShared(document); + save = MakeShared(document, context); } - setLinks(std::move(open), std::move(save), MakeShared(document)); + setLinks( + std::move(open), + std::move(save), + MakeShared(document, context)); } // >= 0 will contain download / upload string, _statusSize = loaded bytes @@ -153,15 +160,19 @@ public: not_null chat, const MTPDphoto &photo, int width); - HistoryPhoto(not_null parent, const HistoryPhoto &other); + HistoryPhoto( + not_null parent, + not_null realParent, + const HistoryPhoto &other); void init(); HistoryMediaType type() const override { return MediaTypePhoto; } std::unique_ptr clone( - not_null newParent) const override { - return std::make_unique(newParent, *this); + not_null newParent, + not_null realParent) const override { + return std::make_unique(newParent, realParent, *this); } void initDimensions() override; @@ -269,14 +280,18 @@ public: not_null parent, not_null document, const QString &caption); - HistoryVideo(not_null parent, const HistoryVideo &other); + HistoryVideo( + not_null parent, + not_null realParent, + const HistoryVideo &other); HistoryMediaType type() const override { return MediaTypeVideo; } std::unique_ptr clone( - not_null newParent) const override { - return std::make_unique(newParent, *this); + not_null newParent, + not_null realParent) const override { + return std::make_unique(newParent, realParent, *this); } void initDimensions() override; @@ -460,7 +475,10 @@ public: : MediaTypeFile); } std::unique_ptr clone( - not_null newParent) const override { + not_null newParent, + not_null realParent) const override { + Expects(newParent == realParent); + return std::make_unique(newParent, *this); } @@ -579,7 +597,10 @@ public: return MediaTypeGif; } std::unique_ptr clone( - not_null newParent) const override { + not_null newParent, + not_null realParent) const override { + Expects(newParent == realParent); + return std::make_unique(newParent, *this); } @@ -696,7 +717,10 @@ public: return MediaTypeSticker; } std::unique_ptr clone( - not_null newParent) const override { + not_null newParent, + not_null realParent) const override { + Expects(newParent == realParent); + return std::make_unique(newParent, _data); } @@ -772,8 +796,16 @@ public: return MediaTypeContact; } std::unique_ptr clone( - not_null newParent) const override { - return std::make_unique(newParent, _userId, _fname, _lname, _phone); + not_null newParent, + not_null realParent) const override { + Expects(newParent == realParent); + + return std::make_unique( + newParent, + _userId, + _fname, + _lname, + _phone); } void initDimensions() override; @@ -840,7 +872,8 @@ public: return MediaTypeCall; } std::unique_ptr clone( - not_null newParent) const override { + not_null newParent, + not_null realParent) const override { Unexpected("Clone HistoryCall."); } @@ -902,7 +935,10 @@ public: return MediaTypeWebPage; } std::unique_ptr clone( - not_null newParent) const override { + not_null newParent, + not_null realParent) const override { + Expects(newParent == realParent); + return std::make_unique(newParent, *this); } @@ -1012,7 +1048,10 @@ public: return MediaTypeGame; } std::unique_ptr clone( - not_null newParent) const override { + not_null newParent, + not_null realParent) const override { + Expects(newParent == realParent); + return std::make_unique(newParent, *this); } @@ -1127,7 +1166,10 @@ public: return MediaTypeInvoice; } std::unique_ptr clone( - not_null newParent) const override { + not_null newParent, + not_null realParent) const override { + Expects(newParent == realParent); + return std::make_unique(newParent, *this); } @@ -1231,7 +1273,10 @@ public: return MediaTypeLocation; } std::unique_ptr clone( - not_null newParent) const override { + not_null newParent, + not_null realParent) const override { + Expects(newParent == realParent); + return std::make_unique(newParent, *this); } diff --git a/Telegram/SourceFiles/history/history_message.cpp b/Telegram/SourceFiles/history/history_message.cpp index 3a8c61a1c8..d32f6189b4 100644 --- a/Telegram/SourceFiles/history/history_message.cpp +++ b/Telegram/SourceFiles/history/history_message.cpp @@ -757,7 +757,7 @@ HistoryMessage::HistoryMessage( return (mediaType != MediaTypeCount); }; if (cloneMedia()) { - _media = mediaOriginal->clone(this); + _media = mediaOriginal->clone(this, this); } setText(fwd->originalText()); } @@ -1512,12 +1512,22 @@ Storage::SharedMediaTypesMask HistoryMessage::sharedMediaTypes() const { TextWithEntities HistoryMessage::selectedText(TextSelection selection) const { TextWithEntities logEntryOriginalResult; - auto textResult = _text.originalTextWithEntities((selection == FullSelection) ? AllTextSelection : selection, ExpandLinksAll); + const auto textSelection = (selection == FullSelection) + ? AllTextSelection + : IsSubGroupSelection(selection) + ? TextSelection(0, 0) + : selection; + auto textResult = _text.originalTextWithEntities( + textSelection, + ExpandLinksAll); auto skipped = skipTextSelection(selection); auto mediaDisplayed = (_media && _media->isDisplayed()); auto mediaResult = mediaDisplayed ? _media->selectedText(skipped) : TextWithEntities(); if (auto entry = Get()) { - logEntryOriginalResult = entry->_page->selectedText(mediaDisplayed ? _media->skipSelection(skipped) : skipped); + const auto originalSelection = mediaDisplayed + ? _media->skipSelection(skipped) + : skipped; + logEntryOriginalResult = entry->_page->selectedText(originalSelection); } auto result = textResult; if (result.text.isEmpty()) { @@ -2232,7 +2242,7 @@ bool HistoryMessage::pointInTime(int right, int bottom, QPoint point, InfoDispla } HistoryTextState HistoryMessage::getState(QPoint point, HistoryStateRequest request) const { - HistoryTextState result; + auto result = HistoryTextState(this); auto g = countGeometry(); if (g.width() < 1) { @@ -2272,7 +2282,9 @@ HistoryTextState HistoryMessage::getState(QPoint point, HistoryStateRequest requ auto entryLeft = g.left(); auto entryTop = trect.y() + trect.height(); 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() + (mediaDisplayed ? _media->fullSelectionLength() : 0); } } @@ -2408,7 +2420,10 @@ void HistoryMessage::updatePressed(QPoint point) { } } -bool HistoryMessage::getStateFromName(QPoint point, QRect &trect, HistoryTextState *outResult) const { +bool HistoryMessage::getStateFromName( + QPoint point, + QRect &trect, + not_null outResult) const { if (displayFromName()) { if (point.y() >= trect.top() && point.y() < trect.top() + st::msgNameFont->height) { auto user = displayFrom(); @@ -2428,7 +2443,11 @@ bool HistoryMessage::getStateFromName(QPoint point, QRect &trect, HistoryTextSta return false; } -bool HistoryMessage::getStateForwardedInfo(QPoint point, QRect &trect, HistoryTextState *outResult, const HistoryStateRequest &request) const { +bool HistoryMessage::getStateForwardedInfo( + QPoint point, + QRect &trect, + not_null outResult, + const HistoryStateRequest &request) const { if (displayForwardedFrom()) { auto forwarded = Get(); auto fwdheight = ((forwarded->_text.maxWidth() > trect.width()) ? 2 : 1) * st::semiboldFont->height; @@ -2438,7 +2457,10 @@ bool HistoryMessage::getStateForwardedInfo(QPoint point, QRect &trect, HistoryTe if (breakEverywhere) { textRequest.flags |= Text::StateRequest::Flag::BreakEverywhere; } - *outResult = forwarded->_text.getState(point - trect.topLeft(), trect.width(), textRequest); + *outResult = HistoryTextState(this, forwarded->_text.getState( + point - trect.topLeft(), + trect.width(), + textRequest)); outResult->symbol = 0; outResult->afterSymbol = false; if (breakEverywhere) { @@ -2453,7 +2475,10 @@ bool HistoryMessage::getStateForwardedInfo(QPoint point, QRect &trect, HistoryTe return false; } -bool HistoryMessage::getStateReplyInfo(QPoint point, QRect &trect, HistoryTextState *outResult) const { +bool HistoryMessage::getStateReplyInfo( + QPoint point, + QRect &trect, + not_null outResult) const { if (auto reply = Get()) { int32 h = st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom(); if (point.y() >= trect.top() && point.y() < trect.top() + h) { @@ -2467,7 +2492,10 @@ bool HistoryMessage::getStateReplyInfo(QPoint point, QRect &trect, HistoryTextSt return false; } -bool HistoryMessage::getStateViaBotIdInfo(QPoint point, QRect &trect, HistoryTextState *outResult) const { +bool HistoryMessage::getStateViaBotIdInfo( + QPoint point, + QRect &trect, + not_null outResult) const { if (!displayFromName() && !Has()) { if (auto via = Get()) { if (QRect(trect.x(), trect.y(), via->_width, st::msgNameFont->height).contains(point)) { @@ -2480,9 +2508,16 @@ bool HistoryMessage::getStateViaBotIdInfo(QPoint point, QRect &trect, HistoryTex return false; } -bool HistoryMessage::getStateText(QPoint point, QRect &trect, HistoryTextState *outResult, const HistoryStateRequest &request) const { +bool HistoryMessage::getStateText( + QPoint point, + QRect &trect, + not_null outResult, + const HistoryStateRequest &request) const { if (trect.contains(point)) { - *outResult = _text.getState(point - trect.topLeft(), trect.width(), request.forText()); + *outResult = HistoryTextState(this, _text.getState( + point - trect.topLeft(), + trect.width(), + request.forText())); return true; } return false; @@ -2524,13 +2559,18 @@ TextSelection HistoryMessage::adjustSelection(TextSelection selection, TextSelec } void HistoryMessage::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) { - if (_media) _media->clickHandlerActiveChanged(p, active); HistoryItem::clickHandlerActiveChanged(p, active); + if (_media) { + _media->clickHandlerActiveChanged(p, active); + } } void HistoryMessage::clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) { - if (_media) _media->clickHandlerPressedChanged(p, pressed); HistoryItem::clickHandlerPressedChanged(p, pressed); + if (_media) { + // HistoryGroupedMedia overrides HistoryItem App::pressedLinkItem(). + _media->clickHandlerPressedChanged(p, pressed); + } } QString HistoryMessage::notificationHeader() const { diff --git a/Telegram/SourceFiles/history/history_message.h b/Telegram/SourceFiles/history/history_message.h index 8b6ae7e908..143c6a41ae 100644 --- a/Telegram/SourceFiles/history/history_message.h +++ b/Telegram/SourceFiles/history/history_message.h @@ -329,11 +329,28 @@ private: void paintViaBotIdInfo(Painter &p, QRect &trect, bool selected) const; void paintText(Painter &p, QRect &trect, TextSelection selection) const; - bool getStateFromName(QPoint point, QRect &trect, HistoryTextState *outResult) const; - bool getStateForwardedInfo(QPoint point, QRect &trect, HistoryTextState *outResult, const HistoryStateRequest &request) const; - bool getStateReplyInfo(QPoint point, QRect &trect, HistoryTextState *outResult) const; - bool getStateViaBotIdInfo(QPoint point, QRect &trect, HistoryTextState *outResult) const; - bool getStateText(QPoint point, QRect &trect, HistoryTextState *outResult, const HistoryStateRequest &request) const; + bool getStateFromName( + QPoint point, + QRect &trect, + not_null outResult) const; + bool getStateForwardedInfo( + QPoint point, + QRect &trect, + not_null outResult, + const HistoryStateRequest &request) const; + bool getStateReplyInfo( + QPoint point, + QRect &trect, + not_null outResult) const; + bool getStateViaBotIdInfo( + QPoint point, + QRect &trect, + not_null outResult) const; + bool getStateText( + QPoint point, + QRect &trect, + not_null outResult, + const HistoryStateRequest &request) const; void setMedia(const MTPMessageMedia *media); void setReplyMarkup(const MTPReplyMarkup *markup); diff --git a/Telegram/SourceFiles/history/history_service.cpp b/Telegram/SourceFiles/history/history_service.cpp index a2feca13f8..bac90fc899 100644 --- a/Telegram/SourceFiles/history/history_service.cpp +++ b/Telegram/SourceFiles/history/history_service.cpp @@ -594,7 +594,7 @@ bool HistoryService::hasPoint(QPoint point) const { } HistoryTextState HistoryService::getState(QPoint point, HistoryStateRequest request) const { - HistoryTextState result; + auto result = HistoryTextState(this); auto g = countGeometry(); if (g.width() < 1) { @@ -618,7 +618,10 @@ HistoryTextState HistoryService::getState(QPoint point, HistoryStateRequest requ if (trect.contains(point)) { auto textRequest = request.forText(); textRequest.align = style::al_center; - result = _text.getState(point - trect.topLeft(), trect.width(), textRequest); + result = HistoryTextState(this, _text.getState( + point - trect.topLeft(), + trect.width(), + textRequest)); if (auto gamescore = Get()) { if (!result.link && result.cursor == HistoryInTextCursorState && g.contains(point)) { result.link = gamescore->lnk; diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp index 51f249b578..31d52fb0dd 100644 --- a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp +++ b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp @@ -218,9 +218,9 @@ HistoryTextState Gif::getState( HistoryStateRequest request) const { if (QRect(0, 0, _width, st::inlineMediaHeight).contains(point)) { if (_delete && rtlpoint(point, _width).x() >= _width - st::stickerPanDeleteIconBg.width() && point.y() < st::stickerPanDeleteIconBg.height()) { - return _delete; + return { nullptr, _delete }; } else { - return _send; + return { nullptr, _send }; } } return {}; @@ -411,7 +411,7 @@ HistoryTextState Sticker::getState( QPoint point, HistoryStateRequest request) const { if (QRect(0, 0, _width, st::inlineMediaHeight).contains(point)) { - return _send; + return { nullptr, _send }; } return {}; } @@ -501,7 +501,7 @@ HistoryTextState Photo::getState( QPoint point, HistoryStateRequest request) const { if (QRect(0, 0, _width, st::inlineMediaHeight).contains(point)) { - return _send; + return { nullptr, _send }; } return {}; } @@ -646,10 +646,10 @@ HistoryTextState Video::getState( QPoint point, HistoryStateRequest request) const { if (QRect(0, st::inlineRowMargin, st::inlineThumbSize, st::inlineThumbSize).contains(point)) { - return _link; + return { nullptr, _link }; } if (QRect(st::inlineThumbSize + st::inlineThumbSkip, 0, _width - st::inlineThumbSize - st::inlineThumbSkip, _height).contains(point)) { - return _send; + return { nullptr, _send }; } return {}; } @@ -787,11 +787,11 @@ HistoryTextState File::getState( QPoint point, HistoryStateRequest request) const { if (QRect(0, st::inlineRowMargin, st::msgFileSize, st::msgFileSize).contains(point)) { - return getShownDocument()->loading() ? _cancel : _open; + return { nullptr, getShownDocument()->loading() ? _cancel : _open }; } else { auto left = st::msgFileSize + st::inlineThumbSkip; if (QRect(left, 0, _width - left, _height).contains(point)) { - return _send; + return { nullptr, _send }; } } return {}; @@ -955,7 +955,7 @@ HistoryTextState Contact::getState( if (!QRect(0, st::inlineRowMargin, st::msgFileSize, st::inlineThumbSize).contains(point)) { auto left = (st::msgFileSize + st::inlineThumbSkip); if (QRect(left, 0, _width - left, _height).contains(point)) { - return _send; + return { nullptr, _send }; } } return {}; @@ -1090,7 +1090,7 @@ HistoryTextState Article::getState( QPoint point, HistoryStateRequest request) const { if (_withThumb && QRect(0, st::inlineRowMargin, st::inlineThumbSize, st::inlineThumbSize).contains(point)) { - return _link; + return { nullptr, _link }; } auto left = _withThumb ? (st::inlineThumbSize + st::inlineThumbSkip) : 0; if (QRect(left, 0, _width - left, _height).contains(point)) { @@ -1100,10 +1100,10 @@ HistoryTextState Article::getState( auto descriptionLines = 2; auto descriptionHeight = qMin(_description.countHeight(_width - left), st::normalFont->height * descriptionLines); if (rtlrect(left, st::inlineRowMargin + titleHeight + descriptionHeight, _urlWidth, st::normalFont->height, _width).contains(point)) { - return _url; + return { nullptr, _url }; } } - return _send; + return { nullptr, _send }; } return {}; } @@ -1275,23 +1275,23 @@ HistoryTextState Game::getState( HistoryStateRequest request) const { int left = st::inlineThumbSize + st::inlineThumbSkip; if (QRect(0, st::inlineRowMargin, st::inlineThumbSize, st::inlineThumbSize).contains(point)) { - return _send; + return { nullptr, _send }; } if (QRect(left, 0, _width - left, _height).contains(point)) { - return _send; + return { nullptr, _send }; } return {}; } void Game::prepareThumb(int width, int height) const { - auto thumb = ([this]() { + auto thumb = [this] { if (auto photo = getResultPhoto()) { return photo->medium; } else if (auto document = getResultDocument()) { return document->thumb; } return ImagePtr(); - })(); + }(); if (thumb->isNull()) { return; } diff --git a/Telegram/SourceFiles/layout.h b/Telegram/SourceFiles/layout.h index b8a99df66b..8ac6cdf1b6 100644 --- a/Telegram/SourceFiles/layout.h +++ b/Telegram/SourceFiles/layout.h @@ -24,6 +24,40 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org constexpr auto FullSelection = TextSelection { 0xFFFF, 0xFFFF }; +inline bool IsSubGroupSelection(TextSelection selection) { + return (selection.from == 0xFFFF) && (selection.to != 0xFFFF); +} + +inline bool IsGroupItemSelection( + TextSelection selection, + int index) { + Expects(index >= 0 && index < 0x0F); + + return IsSubGroupSelection(selection) && (selection.to & (1 << index)); +} + +inline [[nodiscard]] TextSelection AddGroupItemSelection( + TextSelection selection, + int index) { + Expects(index >= 0 && index < 0x0F); + + const auto bit = uint16(1U << index); + return TextSelection( + 0xFFFF, + IsSubGroupSelection(selection) ? (selection.to | bit) : bit); +} + +inline[[nodiscard]] TextSelection RemoveGroupItemSelection( + TextSelection selection, + int index) { + Expects(index >= 0 && index < 0x0F); + + const auto bit = uint16(1U << index); + return IsSubGroupSelection(selection) + ? TextSelection(0xFFFF, selection.to & ~bit) + : selection; +} + extern TextParseOptions _textNameOptions, _textDlgOptions; extern TextParseOptions _historyTextOptions, _historyBotOptions, _historyTextNoMonoOptions, _historyBotNoMonoOptions; diff --git a/Telegram/SourceFiles/messenger.cpp b/Telegram/SourceFiles/messenger.cpp index a4d4facd89..9fa8ffc838 100644 --- a/Telegram/SourceFiles/messenger.cpp +++ b/Telegram/SourceFiles/messenger.cpp @@ -201,9 +201,11 @@ bool Messenger::hideMediaView() { return false; } -void Messenger::showPhoto(not_null link, HistoryItem *item) { - return (!item && link->peer()) - ? showPhoto(link->photo(), link->peer()) +void Messenger::showPhoto(not_null link) { + const auto item = App::histItemById(link->context()); + const auto peer = link->peer(); + return (!item && peer) + ? showPhoto(link->photo(), peer) : showPhoto(link->photo(), item); } @@ -214,7 +216,9 @@ void Messenger::showPhoto(not_null photo, HistoryItem *item) { _mediaView->setFocus(); } -void Messenger::showPhoto(not_null photo, PeerData *peer) { +void Messenger::showPhoto( + not_null photo, + not_null peer) { if (_mediaView->isHidden()) Ui::hideLayer(anim::type::instant); _mediaView->showPhoto(photo, peer); _mediaView->activateWindow(); diff --git a/Telegram/SourceFiles/messenger.h b/Telegram/SourceFiles/messenger.h index 97da6bc83a..1eb423ee9c 100644 --- a/Telegram/SourceFiles/messenger.h +++ b/Telegram/SourceFiles/messenger.h @@ -86,9 +86,9 @@ public: // MediaView interface. void checkMediaViewActivation(); bool hideMediaView(); - void showPhoto(not_null link, HistoryItem *item = nullptr); + void showPhoto(not_null link); void showPhoto(not_null photo, HistoryItem *item); - void showPhoto(not_null photo, PeerData *item); + void showPhoto(not_null photo, not_null item); void showDocument(not_null document, HistoryItem *item); PeerData *ui_getPeerForMouseAction(); diff --git a/Telegram/SourceFiles/overview/overview_layout.cpp b/Telegram/SourceFiles/overview/overview_layout.cpp index a8abfb80fa..0d5c18a742 100644 --- a/Telegram/SourceFiles/overview/overview_layout.cpp +++ b/Telegram/SourceFiles/overview/overview_layout.cpp @@ -283,7 +283,7 @@ Photo::Photo( not_null photo) : ItemBase(parent) , _data(photo) -, _link(MakeShared(photo)) { +, _link(MakeShared(photo, parent->fullId())) { } void Photo::initDimensions() { @@ -352,7 +352,7 @@ HistoryTextState Photo::getState( QPoint point, HistoryStateRequest request) const { if (hasPoint(point)) { - return _link; + return { parent(), _link }; } return {}; } @@ -508,7 +508,12 @@ HistoryTextState Video::getState( bool loaded = _data->loaded(); if (hasPoint(point)) { - return loaded ? _openl : (_data->loading() ? _cancell : _savel); + const auto link = loaded + ? _openl + : _data->loading() + ? _cancell + : _savel; + return { parent(), link }; } return {}; } @@ -687,13 +692,14 @@ HistoryTextState Voice::getState( _st.songThumbSize, _width); if (inner.contains(point)) { - return loaded + const auto link = loaded ? _openl - : ((_data->loading() || _data->status == FileUploading) - ? _cancell - : _openl); + : (_data->loading() || _data->status == FileUploading) + ? _cancell + : _openl; + return { parent(), link }; } - auto result = HistoryTextState(); + auto result = HistoryTextState(parent()); const auto statusmaxwidth = _width - nameleft - nameright; const auto statusrect = rtlrect( nameleft, @@ -718,7 +724,7 @@ HistoryTextState Voice::getState( st::normalFont->height, _width); if (namerect.contains(point) && !result.link && !_data->loading()) { - return _namel; + return { parent(), _namel }; } return result; } @@ -1014,11 +1020,12 @@ HistoryTextState Document::getState( _st.songThumbSize, _width); if (inner.contains(point)) { - return loaded + const auto link = loaded ? _openl - : ((_data->loading() || _data->status == FileUploading) - ? _cancell - : _openl); + : (_data->loading() || _data->status == FileUploading) + ? _cancell + : _openl; + return { parent(), link }; } const auto namerect = rtlrect( nameleft, @@ -1027,7 +1034,7 @@ HistoryTextState Document::getState( st::semiboldFont->height, _width); if (namerect.contains(point) && !_data->loading()) { - return _namel; + return { parent(), _namel }; } } else { const auto nameleft = _st.fileThumbSize + _st.filePadding.right(); @@ -1047,11 +1054,12 @@ HistoryTextState Document::getState( _width); if (rthumb.contains(point)) { - return loaded + const auto link = loaded ? _openl - : ((_data->loading() || _data->status == FileUploading) - ? _cancell - : _savel); + : (_data->loading() || _data->status == FileUploading) + ? _cancell + : _savel; + return { parent(), link }; } if (_data->status != FileUploadFailed) { @@ -1062,7 +1070,7 @@ HistoryTextState Document::getState( st::normalFont->height, _width); if (daterect.contains(point)) { - return _msgl; + return { parent(), _msgl }; } } if (!_data->loading() && _data->isValid()) { @@ -1073,7 +1081,7 @@ HistoryTextState Document::getState( _height - st::linksBorder, _width); if (loaded && leftofnamerect.contains(point)) { - return _namel; + return { parent(), _namel }; } const auto namerect = rtlrect( nameleft, @@ -1082,7 +1090,7 @@ HistoryTextState Document::getState( st::semiboldFont->height, _width); if (namerect.contains(point)) { - return _namel; + return { parent(), _namel }; } } } @@ -1212,7 +1220,9 @@ Link::Link( if (_page->type == WebPageProfile || _page->type == WebPageVideo) { _photol = MakeShared(_page->url); } else if (_page->type == WebPagePhoto || _page->siteName == qstr("Twitter") || _page->siteName == qstr("Facebook")) { - _photol = MakeShared(_page->photo); + _photol = MakeShared( + _page->photo, + parent->fullId()); } else { _photol = MakeShared(_page->url); } @@ -1415,7 +1425,7 @@ HistoryTextState Link::getState( HistoryStateRequest request) const { int32 left = st::linksPhotoSize + st::linksPhotoPadding, top = st::linksMargin.top() + st::linksBorder, w = _width - left; if (rtlrect(0, top, st::linksPhotoSize, st::linksPhotoSize, _width).contains(point)) { - return _photol; + return { parent(), _photol }; } if (!_title.isEmpty() && _text.isEmpty() && _links.size() == 1) { @@ -1423,7 +1433,7 @@ HistoryTextState Link::getState( } if (!_title.isEmpty()) { if (rtlrect(left, top, qMin(w, _titlew), st::semiboldFont->height, _width).contains(point)) { - return _photol; + return { parent(), _photol }; } top += st::webPageTitleFont->height; } @@ -1432,7 +1442,7 @@ HistoryTextState Link::getState( } for (int32 i = 0, l = _links.size(); i < l; ++i) { if (rtlrect(left, top, qMin(w, _links.at(i).width), st::normalFont->height, _width).contains(point)) { - return ClickHandlerPtr(_links[i].lnk); + return { parent(), ClickHandlerPtr(_links[i].lnk) }; } top += st::normalFont->height; } diff --git a/Telegram/SourceFiles/settings/settings_cover.cpp b/Telegram/SourceFiles/settings/settings_cover.cpp index 5c6da4b0e8..e292bb4846 100644 --- a/Telegram/SourceFiles/settings/settings_cover.cpp +++ b/Telegram/SourceFiles/settings/settings_cover.cpp @@ -94,6 +94,8 @@ CoverWidget::CoverWidget(QWidget *parent, UserData *self) } PhotoData *CoverWidget::validatePhoto() const { + Expects(_self != nullptr); + const auto photo = _self->userpicPhotoId() ? App::photo(_self->userpicPhotoId()) : nullptr; @@ -106,7 +108,7 @@ PhotoData *CoverWidget::validatePhoto() const { } void CoverWidget::showPhoto() { - if (auto photo = validatePhoto()) { + if (const auto photo = validatePhoto()) { Messenger::Instance().showPhoto(photo, _self); } }