From 2a6b5cbde5ec3c3603b9cd3aecd5e6eac06eb1fc Mon Sep 17 00:00:00 2001 From: Zi Ree Date: Fri, 21 Nov 2025 00:11:32 +0100 Subject: [PATCH] Add menu entries for copying mentions URIs and inserting mentions into chats from the participants list or avatar URIs, even when the avatar does not come up in the @mention chooser. --- indra/llui/llchatentry.cpp | 15 ++++++++ indra/llui/llchatentry.h | 3 ++ indra/llui/lltextbase.cpp | 20 ++++++++++ indra/llui/lltextbase.h | 3 ++ indra/newview/fschathistory.cpp | 38 +++++++++++++++++++ indra/newview/fschathistory.h | 2 + indra/newview/fscontactsfriendsmenu.cpp | 6 +++ indra/newview/fscontactsfriendsmenu.h | 1 + indra/newview/fspanelimcontrolpanel.cpp | 11 ++++++ indra/newview/fspanelimcontrolpanel.h | 1 + indra/newview/fsparticipantlist.cpp | 23 +++++++++++ indra/newview/fsparticipantlist.h | 8 ++++ .../skins/default/xui/en/menu_avatar_icon.xml | 20 ++++++++++ .../xui/en/menu_fs_contacts_friends.xml | 9 +++++ .../default/xui/en/menu_participant_list.xml | 17 +++++++++ .../skins/default/xui/en/menu_url_agent.xml | 19 ++++++++++ 16 files changed, 196 insertions(+) diff --git a/indra/llui/llchatentry.cpp b/indra/llui/llchatentry.cpp index 9dbbca976b..02a28b4317 100644 --- a/indra/llui/llchatentry.cpp +++ b/indra/llui/llchatentry.cpp @@ -278,3 +278,18 @@ void LLChatEntry::paste() } } // + +// Add menu items to copy and/or insert mention URIs into chat +void LLChatEntry::insertMentionAtCursor(const std::string& str) +{ + S32 cursor_from_end = getLength() - getCursorPos(); + + insertText(str); + + std::string new_text(wstring_to_utf8str(getConvertedText())); + clear(); + appendTextImpl(new_text, LLStyle::Params(), true); + + setCursorPos(getLength() - cursor_from_end); +} +// diff --git a/indra/llui/llchatentry.h b/indra/llui/llchatentry.h index b87b15956c..7cffe854bb 100644 --- a/indra/llui/llchatentry.h +++ b/indra/llui/llchatentry.h @@ -71,6 +71,9 @@ public: // Changed to public so we can update history when using modifier keys void updateHistory(); + // Add menu items to copy and/or insert mention URIs into chat + void insertMentionAtCursor(const std::string& str); + // Fix linefeed pasting /*virtual*/ void paste(); diff --git a/indra/llui/lltextbase.cpp b/indra/llui/lltextbase.cpp index c28b7d6070..15f80e0072 100644 --- a/indra/llui/lltextbase.cpp +++ b/indra/llui/lltextbase.cpp @@ -2451,6 +2451,11 @@ void LLTextBase::createUrlContextMenu(S32 x, S32 y, const std::string &in_url) enable_registrar.add("FS.EnableViewLog", std::bind(&FSRegistrarUtils::checkIsEnabled, gFSRegistrarUtils, target_id, EFSRegistrarFunctionActionType::FS_RGSTR_ACT_VIEW_TRANSCRIPT)); // + // Add menu items to copy and/or insert mention URIs into chat + registrar.add("Mention.CopyURI", boost::bind(&LLUrlAction::copyURLToClipboard, "secondlife:///app/agent/" + target_id_str + "/mention")); + registrar.add("Mention.Chat", boost::bind(&LLTextBase::insertMentionAtCursor, this, "secondlife:///app/agent/" + target_id_str + "/mention")); + // + // FIRE-30725 - Add more group functions to group URL context menu registrar.add("FS.JoinGroup", std::bind(&LLUrlAction::executeSLURL, "secondlife:///app/firestorm/" + target_id_str + "/groupjoin", true)); registrar.add("FS.LeaveGroup", std::bind(&LLUrlAction::executeSLURL, "secondlife:///app/firestorm/" + target_id_str + "/groupleave", true)); @@ -2521,6 +2526,14 @@ void LLTextBase::createUrlContextMenu(S32 x, S32 y, const std::string &in_url) } // + // Add menu items to copy and/or insert mention URIs into chat + if (!parent_floater || (parent_floater->getName() != "panel_im" && parent_floater->getName() != "nearby_chat")) + { + menu->getChild("MentionURISeparator")->setVisible(false); + menu->getChild("mention_in_chat")->setVisible(false); + } + // + menu->show(x, y); LLMenuGL::showPopup(this, menu, x, y); } @@ -4577,3 +4590,10 @@ void LLTextBase::setWordWrap(bool wrap) { mWordWrap = wrap; } + +// Add menu items to copy and/or insert mention URIs into chat +// virtual +void LLTextBase::insertMentionAtCursor(const std::string& str) +{ +} +// diff --git a/indra/llui/lltextbase.h b/indra/llui/lltextbase.h index 87a5961b7d..b5ce3c2bd2 100644 --- a/indra/llui/lltextbase.h +++ b/indra/llui/lltextbase.h @@ -727,6 +727,9 @@ protected: void appendAndHighlightTextImpl(const std::string &new_text, S32 highlight_part, const LLStyle::Params& style_params, e_underline underline_link = e_underline::UNDERLINE_ALWAYS); S32 normalizeUri(std::string& uri); + // Add menu items to copy and/or insert mention URIs into chat + virtual void insertMentionAtCursor(const std::string& str); + protected: // virtual std::string _getSearchText() const override diff --git a/indra/newview/fschathistory.cpp b/indra/newview/fschathistory.cpp index dfb0891dc3..45a66e44b5 100644 --- a/indra/newview/fschathistory.cpp +++ b/indra/newview/fschathistory.cpp @@ -128,6 +128,8 @@ LLObjectIMHandler gObjectIMHandler; class FSChatHistoryHeader: public LLPanel { public: + typedef boost::function insert_mention_callback_t; + FSChatHistoryHeader() : LLPanel(), mInfoCtrl(NULL), @@ -150,6 +152,7 @@ public: mTimeBoxTextBox(NULL), mHeaderLayoutStack(NULL), mAvatarNameCacheConnection(), + mInsertMentionCallback(NULL), mTime(0) {} @@ -180,6 +183,24 @@ public: } } + void setInsertMentionCallback(insert_mention_callback_t cb) + { + mInsertMentionCallback = cb; + } + + void copyURLToClipboard() + { + LLUrlAction::copyURLToClipboard("secondlife:///app/agent/" + mAvatarID.asString() + "/mention"); + } + + void insertMentionAtCursor() + { + if (mInsertMentionCallback) + { + mInsertMentionCallback("secondlife:///app/agent/" + mAvatarID.asString() + "/mention"); + } + } + bool handleMouseUp(S32 x, S32 y, MASK mask) { return LLPanel::handleMouseUp(x,y,mask); @@ -1069,6 +1090,9 @@ protected: registrar_enable.add("AvatarIcon.Enable", boost::bind(&FSChatHistoryHeader::onAvatarIconContextMenuItemEnabled, this, _2)); registrar_enable.add("AvatarIcon.Visible", boost::bind(&FSChatHistoryHeader::onAvatarIconContextMenuItemVisible, this, _2)); + registrar.add("Mention.CopyURI", boost::bind(&FSChatHistoryHeader::copyURLToClipboard, this)); + registrar.add("Mention.Chat", boost::bind(&FSChatHistoryHeader::insertMentionAtCursor, this)); + menu = LLUICtrlFactory::getInstance()->createFromFile("menu_avatar_icon.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); if (menu) { @@ -1256,6 +1280,8 @@ protected: private: boost::signals2::connection mAvatarNameCacheConnection; + + insert_mention_callback_t mInsertMentionCallback; }; FSChatHistory::FSChatHistory(const FSChatHistory::Params& p) @@ -1353,6 +1379,17 @@ void FSChatHistory::initFromParams(const FSChatHistory::Params& p) setShowContextMenu(true); } +// virtual +void FSChatHistory::insertMentionAtCursor(const std::string& str) +{ + updateChatInputLine(); + if (mChatInputLine) + { + mChatInputLine->insertMentionAtCursor(str); + mChatInputLine->setFocus(true); + } +} + LLView* FSChatHistory::getSeparator() { LLPanel* separator = LLUICtrlFactory::getInstance()->createFromFile(mMessageSeparatorFilename, NULL, LLPanel::child_registry_t::instance()); @@ -1365,6 +1402,7 @@ LLView* FSChatHistory::getHeader(const LLChat& chat,const LLStyle::Params& style if (header) { header->setup(chat, style_params, args); + header->setInsertMentionCallback(boost::bind(&FSChatHistory::insertMentionAtCursor, this, _1)); } return header; } diff --git a/indra/newview/fschathistory.h b/indra/newview/fschathistory.h index eea5f15067..a041d57ed0 100644 --- a/indra/newview/fschathistory.h +++ b/indra/newview/fschathistory.h @@ -110,6 +110,8 @@ class FSChatHistory : public LLTextEditor // FIRE-8600: TAB out of cha LLSD getValue() const; void initFromParams(const Params&); + virtual void insertMentionAtCursor(const std::string& str); + /** * Appends a widget message. * If last user appended message, concurs with current user, diff --git a/indra/newview/fscontactsfriendsmenu.cpp b/indra/newview/fscontactsfriendsmenu.cpp index 120d102af9..3573ac0fa0 100644 --- a/indra/newview/fscontactsfriendsmenu.cpp +++ b/indra/newview/fscontactsfriendsmenu.cpp @@ -61,6 +61,7 @@ LLContextMenu* FSContactsFriendsMenu::createMenu() registrar.add("Contacts.Friends.CopyLabel", boost::bind(&FSContactsFriendsMenu::copyNameToClipboard, this, id)); registrar.add("Contacts.Friends.CopyUrl", boost::bind(&FSContactsFriendsMenu::copySLURLToClipboard, this, id)); registrar.add("Contacts.Friends.SelectOption", boost::bind(&FSContactsFriendsMenu::selectOption, this, _2)); + registrar.add("Mention.CopyURI", boost::bind(&FSContactsFriendsMenu::copyURLToClipboard, this)); enable_registrar.add("Contacts.Friends.EnableItem", boost::bind(&FSContactsFriendsMenu::enableContextMenuItem, this, _2)); enable_registrar.add("Contacts.Friends.EnableZoomIn", boost::bind(&LLAvatarActions::canZoomIn, id)); @@ -237,3 +238,8 @@ bool FSContactsFriendsMenu::checkOption(const LLSD& userdata) return false; } + +void FSContactsFriendsMenu::copyURLToClipboard() +{ + LLUrlAction::copyURLToClipboard("secondlife:///app/agent/" + mUUIDs.front().asString() + "/mention"); +} diff --git a/indra/newview/fscontactsfriendsmenu.h b/indra/newview/fscontactsfriendsmenu.h index d8c0e21e6f..7c3dcff836 100644 --- a/indra/newview/fscontactsfriendsmenu.h +++ b/indra/newview/fscontactsfriendsmenu.h @@ -44,6 +44,7 @@ private: void copySLURLToClipboard(const LLUUID& id); void selectOption(const LLSD& userdata); bool checkOption(const LLSD& userdata); + void copyURLToClipboard(); }; extern FSContactsFriendsMenu gFSContactsFriendsMenu; diff --git a/indra/newview/fspanelimcontrolpanel.cpp b/indra/newview/fspanelimcontrolpanel.cpp index 88a1b22ada..17a3351845 100644 --- a/indra/newview/fspanelimcontrolpanel.cpp +++ b/indra/newview/fspanelimcontrolpanel.cpp @@ -32,6 +32,8 @@ #include "fsparticipantlist.h" #include "llagent.h" +#include "llchatentry.h" +#include "fsfloaterim.h" #include "llimview.h" #include "llspeakers.h" @@ -88,9 +90,18 @@ void FSPanelGroupControlPanel::setSessionId(const LLUUID& session_id) return; mParticipantList = new FSParticipantList(speaker_manager, getChild("grp_speakers_list"), true,false); + if (mParticipantList) + { + mParticipantList->setInsertMentionCallback(boost::bind(&FSPanelGroupControlPanel::insertMentionAtCursor, this, _1)); + } } } +void FSPanelGroupControlPanel::insertMentionAtCursor(const LLUUID& avatar_id) +{ + FSFloaterIM::getInstance(getSessionId())->findChild("chat_editor")->insertMentionAtCursor("secondlife:///app/agent/" + avatar_id.asString() + "/mention"); +} + uuid_vec_t FSPanelGroupControlPanel::getParticipants() const { return mParticipantList->getAvatarIds(); diff --git a/indra/newview/fspanelimcontrolpanel.h b/indra/newview/fspanelimcontrolpanel.h index f1877e9284..7ea3c8cedd 100644 --- a/indra/newview/fspanelimcontrolpanel.h +++ b/indra/newview/fspanelimcontrolpanel.h @@ -69,6 +69,7 @@ public: void draw() override; uuid_vec_t getParticipants() const override; + void insertMentionAtCursor(const LLUUID& url); protected: LLUUID mGroupID; diff --git a/indra/newview/fsparticipantlist.cpp b/indra/newview/fsparticipantlist.cpp index 4f3611dbc5..30229b52bc 100644 --- a/indra/newview/fsparticipantlist.cpp +++ b/indra/newview/fsparticipantlist.cpp @@ -39,6 +39,7 @@ #include "llnotificationsutil.h" #include "lloutputmonitorctrl.h" #include "llspeakers.h" +#include "llurlaction.h" #include "llviewercontrol.h" #include "llviewermenu.h" #include "llvoiceclient.h" @@ -69,6 +70,7 @@ FSParticipantList::FSParticipantList(LLSpeakerMgr* data_source, mParticipantListMenu(NULL), mExcludeAgent(exclude_agent), mValidateSpeakerCallback(NULL), + mInsertMentionCallback(NULL), mConvType(CONV_UNKNOWN) { mSpeakerAddListener = new SpeakerAddListener(*this); @@ -301,6 +303,11 @@ void FSParticipantList::setValidateSpeakerCallback(validate_speaker_callback_t c mValidateSpeakerCallback = cb; } +void FSParticipantList::setInsertMentionCallback(insert_mention_callback_t cb) +{ + mInsertMentionCallback = cb; +} + void FSParticipantList::update() { mSpeakerMgr->update(true); @@ -530,6 +537,9 @@ LLContextMenu* FSParticipantList::FSParticipantListMenu::createMenu() registrar.add("ParticipantList.ModerateVoice", boost::bind(&FSParticipantList::FSParticipantListMenu::moderateVoice, this, _2)); + registrar.add("Mention.CopyURI", boost::bind(&FSParticipantList::FSParticipantListMenu::copyURLToClipboard, this, mUUIDs.front())); + registrar.add("Mention.Chat", boost::bind(&FSParticipantList::FSParticipantListMenu::insertMentionAtCursor, this, mUUIDs.front())); + enable_registrar.add("ParticipantList.EnableItem", boost::bind(&FSParticipantList::FSParticipantListMenu::enableContextMenuItem, this, _2)); enable_registrar.add("ParticipantList.EnableItem.Moderate", boost::bind(&FSParticipantList::FSParticipantListMenu::enableModerateContextMenuItem, this, _2)); enable_registrar.add("ParticipantList.CheckItem", boost::bind(&FSParticipantList::FSParticipantListMenu::checkContextMenuItem, this, _2)); @@ -556,6 +566,19 @@ LLContextMenu* FSParticipantList::FSParticipantListMenu::createMenu() return main_menu; } +void FSParticipantList::FSParticipantListMenu::copyURLToClipboard(const LLUUID& avatar_id) +{ + LLUrlAction::copyURLToClipboard("secondlife:///app/agent/" + mUUIDs.front().asString() + "/mention"); +} + +void FSParticipantList::FSParticipantListMenu::insertMentionAtCursor(const LLUUID& avatar_id) +{ + if (mParent.mInsertMentionCallback) + { + mParent.mInsertMentionCallback(avatar_id); + } +} + void FSParticipantList::FSParticipantListMenu::show(LLView* spawning_view, const uuid_vec_t& uuids, S32 x, S32 y) { if (uuids.size() == 0) return; diff --git a/indra/newview/fsparticipantlist.h b/indra/newview/fsparticipantlist.h index e206d2d72c..0427b27083 100644 --- a/indra/newview/fsparticipantlist.h +++ b/indra/newview/fsparticipantlist.h @@ -53,6 +53,7 @@ public: }; typedef boost::function validate_speaker_callback_t; + typedef boost::function insert_mention_callback_t; FSParticipantList(LLSpeakerMgr* data_source, LLAvatarList* avatar_list, @@ -94,6 +95,7 @@ public: * @see onAddItemEvent() */ void setValidateSpeakerCallback(validate_speaker_callback_t cb); + void setInsertMentionCallback(insert_mention_callback_t cb); EConversationType const getType() const { return mConvType; } @@ -240,6 +242,10 @@ protected: static void confirmMuteAllCallback(const LLSD& notification, const LLSD& response); void handleAddToContactSet(); + + // mentions support + void copyURLToClipboard(const LLUUID& avatar_id); + void insertMentionAtCursor(const LLUUID& avatar_id); }; /** @@ -300,6 +306,8 @@ private: LLPointer mSortByRecentSpeakers; validate_speaker_callback_t mValidateSpeakerCallback; + insert_mention_callback_t mInsertMentionCallback; + EConversationType mConvType; }; diff --git a/indra/newview/skins/default/xui/en/menu_avatar_icon.xml b/indra/newview/skins/default/xui/en/menu_avatar_icon.xml index 36e3f85472..de8e0e11e1 100644 --- a/indra/newview/skins/default/xui/en/menu_avatar_icon.xml +++ b/indra/newview/skins/default/xui/en/menu_avatar_icon.xml @@ -129,6 +129,14 @@ name="copy url"> + + + + + + + + + + + diff --git a/indra/newview/skins/default/xui/en/menu_fs_contacts_friends.xml b/indra/newview/skins/default/xui/en/menu_fs_contacts_friends.xml index 6451ccb2c5..8dd6be75f9 100644 --- a/indra/newview/skins/default/xui/en/menu_fs_contacts_friends.xml +++ b/indra/newview/skins/default/xui/en/menu_fs_contacts_friends.xml @@ -115,6 +115,15 @@ + + + + + + + + + + + + + + diff --git a/indra/newview/skins/default/xui/en/menu_url_agent.xml b/indra/newview/skins/default/xui/en/menu_url_agent.xml index 4dfe325405..ff41e69414 100644 --- a/indra/newview/skins/default/xui/en/menu_url_agent.xml +++ b/indra/newview/skins/default/xui/en/menu_url_agent.xml @@ -177,4 +177,23 @@ + + + + + + + + +