From 5f037462ed7032e5bb7f43c7278b538647af90d1 Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Mon, 4 Nov 2024 16:54:01 +0400
Subject: [PATCH] Implement a special context menu for sender userpic.

---
 Telegram/Resources/langs/lang.strings         |  3 ++
 .../history/history_inner_widget.cpp          | 14 ++++++
 .../history/history_inner_widget.h            |  1 +
 .../SourceFiles/history/history_widget.cpp    | 18 ++++++-
 Telegram/SourceFiles/history/history_widget.h |  9 +++-
 .../view/history_view_sublist_section.cpp     |  5 +-
 .../view/history_view_sublist_section.h       |  5 +-
 Telegram/SourceFiles/mainwidget.cpp           | 15 ++++--
 Telegram/SourceFiles/mainwidget.h             |  5 +-
 Telegram/SourceFiles/window/section_widget.h  |  5 +-
 .../SourceFiles/window/window_peer_menu.cpp   | 47 +++++++++++++++++++
 .../SourceFiles/window/window_peer_menu.h     |  8 ++++
 .../window/window_session_controller.cpp      | 11 +++--
 .../window/window_session_controller.h        |  7 ++-
 14 files changed, 137 insertions(+), 16 deletions(-)

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<Ui::PopupMenu>(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<Ui::PopupMenu*> menu,
+		not_null<PeerData*> 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<HistoryItem*> item,
 		const TextSelection &selection);
 
+	void fillSenderUserpicMenu(
+		not_null<Ui::PopupMenu*> menu,
+		not_null<PeerData*> 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<SessionController*> controller,
+		not_null<PeerData*> 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<Data::Thread*> 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<SessionController*> controller,
+	not_null<PeerData*> peer,
+	Ui::InputField *fieldForMention,
+	Dialogs::Key searchInEntry,
+	const PeerMenuCallback &addAction);
+
 void MenuAddMarkAsReadAllChatsAction(
 	not_null<Window::SessionController*> 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<Info::Memento>(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 &params = 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<ChannelData*> channel);