diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index ce54557e1..7b9833592 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -3623,6 +3623,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_context_make_paid" = "Make This Content Paid"; "lng_context_change_price" = "Change Price"; +"lng_context_mention" = "Mention"; +"lng_context_search_from" = "Search messages"; + "lng_factcheck_title" = "Fact Check"; "lng_factcheck_placeholder" = "Add Facts or Context"; "lng_factcheck_whats_this" = "what's this?"; diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index 517006172..9ccd9feb2 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -2204,6 +2204,9 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { const auto linkPhoneNumber = link ? link->property(kPhoneNumberLinkProperty).toString() : QString(); + const auto linkUserpicPeerId = (link && _dragStateUserpic) + ? link->property(kPeerLinkPeerIdProperty).toULongLong() + : 0; const auto session = &this->session(); _whoReactedMenuLifetime.destroy(); if (!clickedReaction.empty() @@ -2227,6 +2230,14 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { return; } _menu = base::make_unique_q(this, st::popupMenuWithIcons); + if (linkUserpicPeerId) { + _widget->fillSenderUserpicMenu( + _menu.get(), + session->data().peer(PeerId(linkUserpicPeerId))); + _menu->popup(e->globalPos()); + e->accept(); + return; + } const auto controller = _controller; const auto addItemActions = [&]( HistoryItem *item, @@ -3938,6 +3949,7 @@ void HistoryInner::mouseActionUpdate() { TextState dragState; ClickHandlerHost *lnkhost = nullptr; + auto dragStateUserpic = false; auto selectingText = (item == _mouseActionItem) && (view == Element::Hovered()) && !_selected.empty() @@ -4033,6 +4045,7 @@ void HistoryInner::mouseActionUpdate() { // stop enumeration if we've found a userpic under the cursor if (point.y() >= userpicTop && point.y() < userpicTop + st::msgPhotoSize) { dragState = TextState(nullptr, view->fromPhotoLink()); + dragStateUserpic = true; _dragStateItem = nullptr; lnkhost = view; return false; @@ -4044,6 +4057,7 @@ void HistoryInner::mouseActionUpdate() { } } auto lnkChanged = ClickHandler::setActive(dragState.link, lnkhost); + _dragStateUserpic = dragStateUserpic; if (lnkChanged || dragState.cursor != _mouseCursorState) { Ui::Tooltip::Hide(); } diff --git a/Telegram/SourceFiles/history/history_inner_widget.h b/Telegram/SourceFiles/history/history_inner_widget.h index c19c3b8b0..e3d671a97 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.h +++ b/Telegram/SourceFiles/history/history_inner_widget.h @@ -499,6 +499,7 @@ private: HistoryItem *_dragStateItem = nullptr; CursorState _mouseCursorState = CursorState(); uint16 _mouseTextSymbol = 0; + bool _dragStateUserpic = false; bool _pressWasInactive = false; bool _recountedAfterPendingResizedItems = false; bool _useCornerReaction = false; diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 16b0035e5..3d067da29 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -29,6 +29,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/emoji_config.h" #include "ui/chat/attach/attach_prepare.h" #include "ui/chat/choose_theme_controller.h" +#include "ui/widgets/menu/menu_add_action_callback_factory.h" #include "ui/widgets/buttons.h" #include "ui/widgets/inner_dropdown.h" #include "ui/widgets/dropdown_menu.h" @@ -5115,7 +5116,10 @@ bool HistoryWidget::updateCmdStartShown() { return commandsChanged || buttonChanged || textChanged; } -bool HistoryWidget::searchInChatEmbedded(Dialogs::Key chat, QString query) { +bool HistoryWidget::searchInChatEmbedded( + QString query, + Dialogs::Key chat, + PeerData *searchFrom) { const auto peer = chat.peer(); // windows todo if (!peer || Window::SeparateId(peer) != controller()->windowId()) { return false; @@ -8071,6 +8075,18 @@ void HistoryWidget::editMessage( setInnerFocus(); } +void HistoryWidget::fillSenderUserpicMenu( + not_null menu, + not_null peer) { + const auto inGroup = _peer && (_peer->isChat() || _peer->isMegagroup()); + Window::FillSenderUserpicMenu( + controller(), + peer, + (inGroup && _canSendTexts) ? _field.data() : nullptr, + inGroup ? _peer->owner().history(_peer) : Dialogs::Key(), + Ui::Menu::CreateAddActionCallback(menu)); +} + void HistoryWidget::hidePinnedMessage() { Expects(_pinnedBar != nullptr); diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h index 86f4c9617..73a8ff1c8 100644 --- a/Telegram/SourceFiles/history/history_widget.h +++ b/Telegram/SourceFiles/history/history_widget.h @@ -211,6 +211,10 @@ public: not_null item, const TextSelection &selection); + void fillSenderUserpicMenu( + not_null menu, + not_null peer); + [[nodiscard]] FullReplyTo replyTo() const; bool lastForceReplyReplied(const FullMsgId &replyTo) const; bool lastForceReplyReplied() const; @@ -263,7 +267,10 @@ public: [[nodiscard]] rpl::producer<> cancelRequests() const { return _cancelRequests.events(); } - bool searchInChatEmbedded(Dialogs::Key chat, QString query); + bool searchInChatEmbedded( + QString query, + Dialogs::Key chat, + PeerData *searchFrom = nullptr); void updateNotifyControls(); diff --git a/Telegram/SourceFiles/history/view/history_view_sublist_section.cpp b/Telegram/SourceFiles/history/view/history_view_sublist_section.cpp index f934a55fe..61215adcf 100644 --- a/Telegram/SourceFiles/history/view/history_view_sublist_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_sublist_section.cpp @@ -329,7 +329,10 @@ void SublistWidget::setInternalState( restoreState(memento); } -bool SublistWidget::searchInChatEmbedded(Dialogs::Key chat, QString query) { +bool SublistWidget::searchInChatEmbedded( + QString query, + Dialogs::Key chat, + PeerData *searchFrom) { const auto sublist = chat.sublist(); if (!sublist || sublist != _sublist) { return false; diff --git a/Telegram/SourceFiles/history/view/history_view_sublist_section.h b/Telegram/SourceFiles/history/view/history_view_sublist_section.h index 6379c9845..33b655720 100644 --- a/Telegram/SourceFiles/history/view/history_view_sublist_section.h +++ b/Telegram/SourceFiles/history/view/history_view_sublist_section.h @@ -76,7 +76,10 @@ public: return Window::SectionActionResult::Fallback; } - bool searchInChatEmbedded(Dialogs::Key chat, QString query) override; + bool searchInChatEmbedded( + QString query, + Dialogs::Key chat, + PeerData *searchFrom = nullptr) override; // Float player interface. bool floatPlayerHandleWheelEvent(QEvent *e) override; diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index ded80eb6d..5fc463f0f 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -744,7 +744,10 @@ void MainWidget::hideSingleUseKeyboard(FullMsgId replyToId) { _history->hideSingleUseKeyboard(replyToId); } -void MainWidget::searchMessages(const QString &query, Dialogs::Key inChat) { +void MainWidget::searchMessages( + const QString &query, + Dialogs::Key inChat, + PeerData *searchFrom) { const auto complex = Data::HashtagWithUsernameFromQuery(query); if (!complex.username.isEmpty()) { _controller->showPeerByLink(Window::PeerByLinkInfo{ @@ -760,6 +763,7 @@ void MainWidget::searchMessages(const QString &query, Dialogs::Key inChat) { .inChat = ((tags.empty() || inChat.sublist()) ? inChat : session().data().history(session().user())), + .fromPeer = inChat ? searchFrom : nullptr, .tags = tags, .query = tags.empty() ? query : QString(), }; @@ -779,12 +783,15 @@ void MainWidget::searchMessages(const QString &query, Dialogs::Key inChat) { controller()->session().user()); } if ((!_mainSection - || !_mainSection->searchInChatEmbedded(inChat, query)) - && !_history->searchInChatEmbedded(inChat, query)) { + || !_mainSection->searchInChatEmbedded(query, inChat, searchFrom)) + && !_history->searchInChatEmbedded(query, inChat, searchFrom)) { const auto account = not_null(&session().account()); if (const auto window = Core::App().windowFor(account)) { if (const auto controller = window->sessionController()) { - controller->content()->searchMessages(query, inChat); + controller->content()->searchMessages( + query, + inChat, + searchFrom); controller->widget()->activate(); } } diff --git a/Telegram/SourceFiles/mainwidget.h b/Telegram/SourceFiles/mainwidget.h index 4465e5685..32e5fff5d 100644 --- a/Telegram/SourceFiles/mainwidget.h +++ b/Telegram/SourceFiles/mainwidget.h @@ -163,7 +163,10 @@ public: void sendBotCommand(Bot::SendCommandRequest request); void hideSingleUseKeyboard(FullMsgId replyToId); - void searchMessages(const QString &query, Dialogs::Key inChat); + void searchMessages( + const QString &query, + Dialogs::Key inChat, + PeerData *searchFrom = nullptr); void setChatBackground( const Data::WallPaper &background, diff --git a/Telegram/SourceFiles/window/section_widget.h b/Telegram/SourceFiles/window/section_widget.h index c428c4d0f..24761f097 100644 --- a/Telegram/SourceFiles/window/section_widget.h +++ b/Telegram/SourceFiles/window/section_widget.h @@ -148,7 +148,10 @@ public: MsgId messageId) { return false; } - virtual bool searchInChatEmbedded(Dialogs::Key chat, QString query) { + virtual bool searchInChatEmbedded( + QString query, + Dialogs::Key chat, + PeerData *searchFrom = nullptr) { return false; } diff --git a/Telegram/SourceFiles/window/window_peer_menu.cpp b/Telegram/SourceFiles/window/window_peer_menu.cpp index 9e66096fd..a053bc87b 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.cpp +++ b/Telegram/SourceFiles/window/window_peer_menu.cpp @@ -2727,6 +2727,53 @@ bool FillVideoChatMenu( return has || manager; } +void FillSenderUserpicMenu( + not_null controller, + not_null peer, + Ui::InputField *fieldForMention, + Dialogs::Key searchInEntry, + const PeerMenuCallback &addAction) { + const auto group = (peer->isChat() || peer->isMegagroup()); + const auto channel = peer->isChannel(); + const auto viewProfileText = group + ? tr::lng_context_view_group(tr::now) + : channel + ? tr::lng_context_view_channel(tr::now) + : tr::lng_context_view_profile(tr::now); + addAction(viewProfileText, [=] { + controller->showPeerInfo(peer, Window::SectionShow::Way::Forward); + }, channel ? &st::menuIconInfo : &st::menuIconProfile); + + const auto showHistoryText = group + ? tr::lng_context_open_group(tr::now) + : channel + ? tr::lng_context_open_channel(tr::now) + : tr::lng_profile_send_message(tr::now); + addAction(showHistoryText, [=] { + controller->showPeerHistory(peer, Window::SectionShow::Way::Forward); + }, channel ? &st::menuIconChannel : &st::menuIconChatBubble); + + const auto username = peer->username(); + const auto mention = !username.isEmpty() || peer->isUser(); + if (const auto guard = mention ? fieldForMention : nullptr) { + addAction(tr::lng_context_mention(tr::now), crl::guard(guard, [=] { + if (!username.isEmpty()) { + fieldForMention->insertTag('@' + username); + } else { + fieldForMention->insertTag( + peer->shortName(), + PrepareMentionTag(peer->asUser())); + } + }), &st::menuIconUsername); + } + + if (searchInEntry) { + addAction(tr::lng_context_search_from(tr::now), [=] { + controller->searchInChat(searchInEntry, peer); + }, &st::menuIconSearch); + } +} + bool IsUnreadThread(not_null thread) { return thread->chatListBadgesState().unread; } diff --git a/Telegram/SourceFiles/window/window_peer_menu.h b/Telegram/SourceFiles/window/window_peer_menu.h index 4bf6c4d13..8369e3228 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.h +++ b/Telegram/SourceFiles/window/window_peer_menu.h @@ -34,6 +34,7 @@ namespace Dialogs { class MainList; struct EntryState; struct UnreadState; +class Key; } // namespace Dialogs namespace ChatHelpers { @@ -64,6 +65,13 @@ bool FillVideoChatMenu( Dialogs::EntryState request, const PeerMenuCallback &addAction); +void FillSenderUserpicMenu( + not_null controller, + not_null peer, + Ui::InputField *fieldForMention, + Dialogs::Key searchInEntry, + const PeerMenuCallback &addAction); + void MenuAddMarkAsReadAllChatsAction( not_null controller, const PeerMenuCallback &addAction); diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index b9d1fc876..69d58b571 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -1175,14 +1175,17 @@ void SessionNavigation::showPollResults( showSection(std::make_shared(poll, contextId), params); } -void SessionNavigation::searchInChat(Dialogs::Key inChat) { - searchMessages(QString(), inChat); +void SessionNavigation::searchInChat( + Dialogs::Key inChat, + PeerData *searchFrom) { + searchMessages(QString(), inChat, searchFrom); } void SessionNavigation::searchMessages( const QString &query, - Dialogs::Key inChat) { - parentController()->content()->searchMessages(query, inChat); + Dialogs::Key inChat, + PeerData *searchFrom) { + parentController()->content()->searchMessages(query, inChat, searchFrom); } auto SessionNavigation::showToast(Ui::Toast::Config &&config) diff --git a/Telegram/SourceFiles/window/window_session_controller.h b/Telegram/SourceFiles/window/window_session_controller.h index 3da9eb291..c925aacc7 100644 --- a/Telegram/SourceFiles/window/window_session_controller.h +++ b/Telegram/SourceFiles/window/window_session_controller.h @@ -247,8 +247,11 @@ public: FullMsgId contextId, const SectionShow ¶ms = SectionShow()); - void searchInChat(Dialogs::Key inChat); - void searchMessages(const QString &query, Dialogs::Key inChat); + void searchInChat(Dialogs::Key inChat, PeerData *searchFrom = nullptr); + void searchMessages( + const QString &query, + Dialogs::Key inChat, + PeerData *searchFrom = nullptr); void resolveBoostState(not_null channel);