diff options
author | Ansariel <ansariel.hiller@phoenixviewer.com> | 2024-05-22 19:04:52 +0200 |
---|---|---|
committer | Ansariel <ansariel.hiller@phoenixviewer.com> | 2024-05-22 19:04:52 +0200 |
commit | 1b67dd855c41f5a0cda7ec2a68d98071986ca703 (patch) | |
tree | ab243607f74f78200787bba5b9b88f07ef1b966f /indra/newview/llchathistory.cpp | |
parent | 6d6eabca44d08d5b97bfe3e941d2b9687c2246ea (diff) | |
parent | e1623bb276f83a43ce7a197e388720c05bdefe61 (diff) |
Merge remote-tracking branch 'origin/main' into DRTVWR-600-maint-A
# Conflicts:
# autobuild.xml
# indra/cmake/CMakeLists.txt
# indra/cmake/GoogleMock.cmake
# indra/llaudio/llaudioengine_fmodstudio.cpp
# indra/llaudio/llaudioengine_fmodstudio.h
# indra/llaudio/lllistener_fmodstudio.cpp
# indra/llaudio/lllistener_fmodstudio.h
# indra/llaudio/llstreamingaudio_fmodstudio.cpp
# indra/llaudio/llstreamingaudio_fmodstudio.h
# indra/llcharacter/llmultigesture.cpp
# indra/llcharacter/llmultigesture.h
# indra/llimage/llimage.cpp
# indra/llimage/llimagepng.cpp
# indra/llimage/llimageworker.cpp
# indra/llimage/tests/llimageworker_test.cpp
# indra/llmessage/tests/llmockhttpclient.h
# indra/llprimitive/llgltfmaterial.h
# indra/llrender/llfontfreetype.cpp
# indra/llui/llcombobox.cpp
# indra/llui/llfolderview.cpp
# indra/llui/llfolderviewmodel.h
# indra/llui/lllineeditor.cpp
# indra/llui/lllineeditor.h
# indra/llui/lltextbase.cpp
# indra/llui/lltextbase.h
# indra/llui/lltexteditor.cpp
# indra/llui/lltextvalidate.cpp
# indra/llui/lltextvalidate.h
# indra/llui/lluictrl.h
# indra/llui/llview.cpp
# indra/llwindow/llwindowmacosx.cpp
# indra/newview/app_settings/settings.xml
# indra/newview/llappearancemgr.cpp
# indra/newview/llappearancemgr.h
# indra/newview/llavatarpropertiesprocessor.cpp
# indra/newview/llavatarpropertiesprocessor.h
# indra/newview/llbreadcrumbview.cpp
# indra/newview/llbreadcrumbview.h
# indra/newview/llbreastmotion.cpp
# indra/newview/llbreastmotion.h
# indra/newview/llconversationmodel.h
# indra/newview/lldensityctrl.cpp
# indra/newview/lldensityctrl.h
# indra/newview/llface.inl
# indra/newview/llfloatereditsky.cpp
# indra/newview/llfloatereditwater.cpp
# indra/newview/llfloateremojipicker.h
# indra/newview/llfloaterimsessiontab.cpp
# indra/newview/llfloaterprofiletexture.cpp
# indra/newview/llfloaterprofiletexture.h
# indra/newview/llgesturemgr.cpp
# indra/newview/llgesturemgr.h
# indra/newview/llimpanel.cpp
# indra/newview/llimpanel.h
# indra/newview/llinventorybridge.cpp
# indra/newview/llinventorybridge.h
# indra/newview/llinventoryclipboard.cpp
# indra/newview/llinventoryclipboard.h
# indra/newview/llinventoryfunctions.cpp
# indra/newview/llinventoryfunctions.h
# indra/newview/llinventorygallery.cpp
# indra/newview/lllistbrowser.cpp
# indra/newview/lllistbrowser.h
# indra/newview/llpanelobjectinventory.cpp
# indra/newview/llpanelprofile.cpp
# indra/newview/llpanelprofile.h
# indra/newview/llpreviewgesture.cpp
# indra/newview/llsavedsettingsglue.cpp
# indra/newview/llsavedsettingsglue.h
# indra/newview/lltooldraganddrop.cpp
# indra/newview/llurllineeditorctrl.cpp
# indra/newview/llvectorperfoptions.cpp
# indra/newview/llvectorperfoptions.h
# indra/newview/llviewerparceloverlay.cpp
# indra/newview/llviewertexlayer.cpp
# indra/newview/llviewertexturelist.cpp
# indra/newview/macmain.h
# indra/test/test.cpp
Diffstat (limited to 'indra/newview/llchathistory.cpp')
-rw-r--r-- | indra/newview/llchathistory.cpp | 3096 |
1 files changed, 1548 insertions, 1548 deletions
diff --git a/indra/newview/llchathistory.cpp b/indra/newview/llchathistory.cpp index 7d9fa2a475..75f3cb11a8 100644 --- a/indra/newview/llchathistory.cpp +++ b/indra/newview/llchathistory.cpp @@ -1,1548 +1,1548 @@ -/** - * @file llchathistory.cpp - * @brief LLTextEditor base class - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llchathistory.h" - -#include <boost/signals2.hpp> - -#include "llavatarnamecache.h" -#include "llinstantmessage.h" - -#include "llimview.h" -#include "llcommandhandler.h" -#include "llpanel.h" -#include "lluictrlfactory.h" -#include "llscrollcontainer.h" -#include "llagent.h" -#include "llagentdata.h" -#include "llavataractions.h" -#include "llavatariconctrl.h" -#include "llcallingcard.h" //for LLAvatarTracker -#include "llgroupactions.h" -#include "llgroupmgr.h" -#include "llspeakers.h" //for LLIMSpeakerMgr -#include "lltrans.h" -#include "llfloaterreg.h" -#include "llfloaterreporter.h" -#include "llfloatersidepanelcontainer.h" -#include "llmutelist.h" -#include "llstylemap.h" -#include "llslurl.h" -#include "lllayoutstack.h" -#include "llnotifications.h" -#include "llnotificationsutil.h" -#include "lltoastnotifypanel.h" -#include "lltooltip.h" -#include "llviewerregion.h" -#include "llviewertexteditor.h" -#include "llworld.h" -#include "lluiconstants.h" -#include "llstring.h" -#include "llurlaction.h" -#include "llviewercontrol.h" -#include "llviewermenu.h" -#include "llviewerobjectlist.h" - -static LLDefaultChildRegistry::Register<LLChatHistory> r("chat_history"); - -const static std::string NEW_LINE(rawstr_to_utf8("\n")); - -const static std::string SLURL_APP_AGENT = "secondlife:///app/agent/"; -const static std::string SLURL_ABOUT = "/about"; - -// support for secondlife:///app/objectim/{UUID}/ SLapps -class LLObjectIMHandler : public LLCommandHandler -{ -public: - // requests will be throttled from a non-trusted browser - LLObjectIMHandler() : LLCommandHandler("objectim", UNTRUSTED_THROTTLE) {} - - bool handle(const LLSD& params, const LLSD& query_map, const std::string& grid, LLMediaCtrl* web) - { - if (params.size() < 1) - { - return false; - } - - LLUUID object_id; - if (!object_id.set(params[0], false)) - { - return false; - } - - LLSD payload; - payload["object_id"] = object_id; - payload["owner_id"] = query_map["owner"]; - payload["name"] = query_map["name"]; - payload["slurl"] = LLWeb::escapeURL(query_map["slurl"]); - payload["group_owned"] = query_map["groupowned"]; - LLFloaterReg::showInstance("inspect_remote_object", payload); - return true; - } -}; -LLObjectIMHandler gObjectIMHandler; - -class LLChatHistoryHeader: public LLPanel -{ -public: - LLChatHistoryHeader() - : LLPanel(), - mInfoCtrl(NULL), - mPopupMenuHandleAvatar(), - mPopupMenuHandleObject(), - mAvatarID(), - mSourceType(CHAT_SOURCE_UNKNOWN), - mFrom(), - mSessionID(), - mCreationTime(time_corrected()), - mMinUserNameWidth(0), - mUserNameFont(NULL), - mUserNameTextBox(NULL), - mTimeBoxTextBox(NULL), - mNeedsTimeBox(true), - mAvatarNameCacheConnection() - {} - - static LLChatHistoryHeader* createInstance(const std::string& file_name) - { - LLChatHistoryHeader* pInstance = new LLChatHistoryHeader; - pInstance->buildFromFile(file_name); - return pInstance; - } - - ~LLChatHistoryHeader() - { - if (mAvatarNameCacheConnection.connected()) - { - mAvatarNameCacheConnection.disconnect(); - } - auto menu = mPopupMenuHandleAvatar.get(); - if (menu) - { - menu->die(); - mPopupMenuHandleAvatar.markDead(); - } - menu = mPopupMenuHandleObject.get(); - if (menu) - { - menu->die(); - mPopupMenuHandleObject.markDead(); - } - } - - bool handleMouseUp(S32 x, S32 y, MASK mask) - { - return LLPanel::handleMouseUp(x,y,mask); - } - - void onObjectIconContextMenuItemClicked(const LLSD& userdata) - { - std::string level = userdata.asString(); - - if (level == "profile") - { - LLFloaterReg::showInstance("inspect_remote_object", mObjectData); - } - else if (level == "block") - { - LLMuteList::getInstance()->add(LLMute(getAvatarId(), mFrom, LLMute::OBJECT)); - - LLFloaterSidePanelContainer::showPanel("people", "panel_people", - LLSD().with("people_panel_tab_name", "blocked_panel").with("blocked_to_select", getAvatarId())); - } - else if (level == "unblock") - { - LLMuteList::getInstance()->remove(LLMute(getAvatarId(), mFrom, LLMute::OBJECT)); - } - else if (level == "map") - { - std::string url = "secondlife://" + mObjectData["slurl"].asString(); - LLUrlAction::showLocationOnMap(url); - } - else if (level == "teleport") - { - std::string url = "secondlife://" + mObjectData["slurl"].asString(); - LLUrlAction::teleportToLocation(url); - } - - } - - bool onObjectIconContextMenuItemVisible(const LLSD& userdata) - { - std::string level = userdata.asString(); - if (level == "is_blocked") - { - return LLMuteList::getInstance()->isMuted(getAvatarId(), mFrom, LLMute::flagTextChat); - } - else if (level == "not_blocked") - { - return !LLMuteList::getInstance()->isMuted(getAvatarId(), mFrom, LLMute::flagTextChat); - } - return false; - } - - void banGroupMember(const LLUUID& participant_uuid) - { - LLUUID group_uuid = mSessionID; - LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(group_uuid); - if (!gdatap) - { - // Not a group - return; - } - - gdatap->banMemberById(participant_uuid); - } - - bool canBanInGroup() - { - LLUUID group_uuid = mSessionID; - LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(group_uuid); - if (!gdatap) - { - // Not a group - return false; - } - - if (gAgent.hasPowerInGroup(group_uuid, GP_ROLE_REMOVE_MEMBER) - && gAgent.hasPowerInGroup(group_uuid, GP_GROUP_BAN_ACCESS)) - { - return true; - } - - return false; - } - - bool canBanGroupMember(const LLUUID& participant_uuid) - { - LLUUID group_uuid = mSessionID; - LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(group_uuid); - if (!gdatap) - { - // Not a group - return false; - } - - if (gdatap->mPendingBanRequest) - { - return false; - } - - if (gAgentID == getAvatarId()) - { - //Don't ban self - return false; - } - - if (gdatap->isRoleMemberDataComplete()) - { - if (gdatap->mMembers.size()) - { - LLGroupMgrGroupData::member_list_t::iterator mi = gdatap->mMembers.find(participant_uuid); - if (mi != gdatap->mMembers.end()) - { - LLGroupMemberData* member_data = (*mi).second; - // Is the member an owner? - if (member_data && member_data->isInRole(gdatap->mOwnerRole)) - { - return false; - } - - if (gAgent.hasPowerInGroup(group_uuid, GP_ROLE_REMOVE_MEMBER) - && gAgent.hasPowerInGroup(group_uuid, GP_GROUP_BAN_ACCESS)) - { - return true; - } - } - } - } - - LLSpeakerMgr * speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(mSessionID); - if (speaker_mgr) - { - LLSpeaker * speakerp = speaker_mgr->findSpeaker(participant_uuid).get(); - - if (speakerp - && gAgent.hasPowerInGroup(group_uuid, GP_ROLE_REMOVE_MEMBER) - && gAgent.hasPowerInGroup(group_uuid, GP_GROUP_BAN_ACCESS)) - { - return true; - } - } - - return false; - } - - bool isGroupModerator() - { - LLSpeakerMgr * speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(mSessionID); - if (!speaker_mgr) - { - LL_WARNS() << "Speaker manager is missing" << LL_ENDL; - return false; - } - - // Is session a group call/chat? - if(gAgent.isInGroup(mSessionID)) - { - LLSpeaker * speakerp = speaker_mgr->findSpeaker(gAgentID).get(); - - // Is agent a moderator? - return speakerp && speakerp->mIsModerator; - } - - return false; - } - - bool canModerate(const std::string& userdata) - { - // only group moderators can perform actions related to this "enable callback" - if (!isGroupModerator() || gAgentID == getAvatarId()) - { - return false; - } - - LLSpeakerMgr * speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(mSessionID); - if (!speaker_mgr) - { - return false; - } - - LLSpeaker * speakerp = speaker_mgr->findSpeaker(getAvatarId()).get(); - if (!speakerp) - { - return false; - } - - bool voice_channel = speakerp->isInVoiceChannel(); - - if ("can_moderate_voice" == userdata) - { - return voice_channel; - } - else if ("can_mute" == userdata) - { - return voice_channel && (speakerp->mStatus != LLSpeaker::STATUS_MUTED); - } - else if ("can_unmute" == userdata) - { - return speakerp->mStatus == LLSpeaker::STATUS_MUTED; - } - else if ("can_allow_text_chat" == userdata) - { - return true; - } - - return false; - } - - void onAvatarIconContextMenuItemClicked(const LLSD& userdata) - { - std::string level = userdata.asString(); - - if (level == "profile") - { - LLAvatarActions::showProfile(getAvatarId()); - } - else if (level == "im") - { - LLAvatarActions::startIM(getAvatarId()); - } - else if (level == "teleport") - { - LLAvatarActions::offerTeleport(getAvatarId()); - } - else if (level == "request_teleport") - { - LLAvatarActions::teleportRequest(getAvatarId()); - } - else if (level == "voice_call") - { - LLAvatarActions::startCall(getAvatarId()); - } - else if (level == "chat_history") - { - LLAvatarActions::viewChatHistory(getAvatarId()); - } - else if (level == "add") - { - LLAvatarActions::requestFriendshipDialog(getAvatarId(), mFrom); - } - else if (level == "remove") - { - LLAvatarActions::removeFriendDialog(getAvatarId()); - } - else if (level == "invite_to_group") - { - LLAvatarActions::inviteToGroup(getAvatarId()); - } - else if (level == "zoom_in") - { - handle_zoom_to_object(getAvatarId()); - } - else if (level == "map") - { - LLAvatarActions::showOnMap(getAvatarId()); - } - else if (level == "share") - { - LLAvatarActions::share(getAvatarId()); - } - else if (level == "pay") - { - LLAvatarActions::pay(getAvatarId()); - } - else if (level == "report_abuse") - { - std::string time_string; - if (mTime > 0) // have frame time - { - time_t current_time = time_corrected(); - time_t message_time = current_time - LLFrameTimer::getElapsedSeconds() + mTime; - - time_string = "[" + LLTrans::getString("TimeMonth") + "]/[" - + LLTrans::getString("TimeDay") + "]/[" - + LLTrans::getString("TimeYear") + "] [" - + LLTrans::getString("TimeHour") + "]:[" - + LLTrans::getString("TimeMin") + "]"; - - LLSD substitution; - - substitution["datetime"] = (S32)message_time; - LLStringUtil::format(time_string, substitution); - } - else - { - // From history. This might be empty or not full. - // See LLChatLogParser::parse - time_string = getChild<LLTextBox>("time_box")->getValue().asString(); - - // Just add current date if not full. - // Should be fine since both times are supposed to be stl - if (!time_string.empty() && time_string.size() < 7) - { - time_string = "[" + LLTrans::getString("TimeMonth") + "]/[" - + LLTrans::getString("TimeDay") + "]/[" - + LLTrans::getString("TimeYear") + "] " + time_string; - - LLSD substitution; - // To avoid adding today's date to yesterday's timestamp, - // use creation time instead of current time - substitution["datetime"] = (S32)mCreationTime; - LLStringUtil::format(time_string, substitution); - } - } - LLFloaterReporter::showFromChat(mAvatarID, mFrom, time_string, mText); - } - else if(level == "block_unblock") - { - LLAvatarActions::toggleMute(getAvatarId(), LLMute::flagVoiceChat); - } - else if(level == "mute_unmute") - { - LLAvatarActions::toggleMute(getAvatarId(), LLMute::flagTextChat); - } - else if(level == "toggle_allow_text_chat") - { - LLIMSpeakerMgr* speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(mSessionID); - speaker_mgr->toggleAllowTextChat(getAvatarId()); - } - else if(level == "group_mute") - { - LLIMSpeakerMgr* speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(mSessionID); - if (speaker_mgr) - { - speaker_mgr->moderateVoiceParticipant(getAvatarId(), false); - } - } - else if(level == "group_unmute") - { - LLIMSpeakerMgr* speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(mSessionID); - if (speaker_mgr) - { - speaker_mgr->moderateVoiceParticipant(getAvatarId(), true); - } - } - else if(level == "ban_member") - { - banGroupMember(getAvatarId()); - } - } - - bool onAvatarIconContextMenuItemChecked(const LLSD& userdata) - { - std::string level = userdata.asString(); - - if (level == "is_blocked") - { - return LLMuteList::getInstance()->isMuted(getAvatarId(), LLMute::flagVoiceChat); - } - if (level == "is_muted") - { - return LLMuteList::getInstance()->isMuted(getAvatarId(), LLMute::flagTextChat); - } - else if (level == "is_allowed_text_chat") - { - if (gAgent.isInGroup(mSessionID)) - { - LLIMSpeakerMgr* speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(mSessionID); - if(speaker_mgr) - { - const LLSpeaker * speakerp = speaker_mgr->findSpeaker(getAvatarId()); - if (NULL != speakerp) - { - return !speakerp->mModeratorMutedText; - } - } - } - return false; - } - return false; - } - - bool onAvatarIconContextMenuItemEnabled(const LLSD& userdata) - { - std::string level = userdata.asString(); - - if (level == "can_allow_text_chat" || level == "can_mute" || level == "can_unmute") - { - return canModerate(userdata); - } - else if (level == "report_abuse") - { - return gAgentID != mAvatarID; - } - else if (level == "can_ban_member") - { - return canBanGroupMember(getAvatarId()); - } - return false; - } - - bool onAvatarIconContextMenuItemVisible(const LLSD& userdata) - { - std::string level = userdata.asString(); - - if (level == "show_mute") - { - LLSpeakerMgr * speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(mSessionID); - if (speaker_mgr) - { - LLSpeaker * speakerp = speaker_mgr->findSpeaker(getAvatarId()).get(); - if (speakerp) - { - return speakerp->isInVoiceChannel() && speakerp->mStatus != LLSpeaker::STATUS_MUTED; - } - } - return false; - } - else if (level == "show_unmute") - { - LLSpeakerMgr * speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(mSessionID); - if (speaker_mgr) - { - LLSpeaker * speakerp = speaker_mgr->findSpeaker(getAvatarId()).get(); - if (speakerp) - { - return speakerp->mStatus == LLSpeaker::STATUS_MUTED; - } - } - return false; - } - return false; - } - - bool postBuild() - { - setDoubleClickCallback(boost::bind(&LLChatHistoryHeader::showInspector, this)); - - setMouseEnterCallback(boost::bind(&LLChatHistoryHeader::showInfoCtrl, this)); - setMouseLeaveCallback(boost::bind(&LLChatHistoryHeader::hideInfoCtrl, this)); - - mUserNameTextBox = getChild<LLTextBox>("user_name"); - mTimeBoxTextBox = getChild<LLTextBox>("time_box"); - - mInfoCtrl = LLUICtrlFactory::getInstance()->createFromFile<LLUICtrl>("inspector_info_ctrl.xml", this, LLPanel::child_registry_t::instance()); - if (mInfoCtrl) - { - mInfoCtrl->setCommitCallback(boost::bind(&LLChatHistoryHeader::onClickInfoCtrl, mInfoCtrl)); - mInfoCtrl->setVisible(false); - } - else - { - LL_ERRS() << "Failed to create an interface element due to missing or corrupted file inspector_info_ctrl.xml" << LL_ENDL; - } - - return LLPanel::postBuild(); - } - - bool pointInChild(const std::string& name,S32 x,S32 y) - { - LLUICtrl* child = findChild<LLUICtrl>(name); - if(!child) - return false; - - LLView* parent = child->getParent(); - if(parent!=this) - { - x-=parent->getRect().mLeft; - y-=parent->getRect().mBottom; - } - - S32 local_x = x - child->getRect().mLeft ; - S32 local_y = y - child->getRect().mBottom ; - return child->pointInView(local_x, local_y); - } - - bool handleRightMouseDown(S32 x, S32 y, MASK mask) - { - if(pointInChild("avatar_icon",x,y) || pointInChild("user_name",x,y)) - { - showContextMenu(x,y); - return true; - } - - return LLPanel::handleRightMouseDown(x,y,mask); - } - - void showInspector() - { - if (mAvatarID.isNull() && CHAT_SOURCE_SYSTEM != mSourceType && CHAT_SOURCE_REGION != mSourceType) return; - - if (mSourceType == CHAT_SOURCE_OBJECT) - { - LLFloaterReg::showInstance("inspect_remote_object", mObjectData); - } - else if (mSourceType == CHAT_SOURCE_AGENT) - { - LLFloaterReg::showInstance("inspect_avatar", LLSD().with("avatar_id", mAvatarID)); - } - //if chat source is system, you may add "else" here to define behaviour. - } - - static void onClickInfoCtrl(LLUICtrl* info_ctrl) - { - if (!info_ctrl) return; - - LLChatHistoryHeader* header = dynamic_cast<LLChatHistoryHeader*>(info_ctrl->getParent()); - if (!header) return; - - header->showInspector(); - } - - - const LLUUID& getAvatarId () const { return mAvatarID;} - - void setup(const LLChat& chat, const LLStyle::Params& style_params, const LLSD& args) - { - mAvatarID = chat.mFromID; - mSessionID = chat.mSessionID; - mSourceType = chat.mSourceType; - - // To be able to report a message, we need a copy of it's text - // and it's easier to store text directly than trying to get - // it from a lltextsegment or chat's mEditor - mText = chat.mText; - mTime = chat.mTime; - - //*TODO overly defensive thing, source type should be maintained out there - if((chat.mFromID.isNull() && chat.mFromName.empty()) || (chat.mFromName == SYSTEM_FROM && chat.mFromID.isNull())) - { - mSourceType = CHAT_SOURCE_SYSTEM; - } - - mUserNameFont = style_params.font(); - if (!mUserNameTextBox) - { - mUserNameTextBox = getChild<LLTextBox>("user_name"); - mTimeBoxTextBox = getChild<LLTextBox>("time_box"); - } - LLTextBox* user_name = mUserNameTextBox; - user_name->setReadOnlyColor(style_params.readonly_color()); - user_name->setColor(style_params.color()); - - if (mSourceType == CHAT_SOURCE_TELEPORT - && chat.mChatStyle == CHAT_STYLE_TELEPORT_SEP) - { - mFrom = chat.mFromName; - mNeedsTimeBox = false; - user_name->setValue(mFrom); - updateMinUserNameWidth(); - LLColor4 sep_color = LLUIColorTable::instance().getColor("ChatTeleportSeparatorColor"); - setTransparentColor(sep_color); - mTimeBoxTextBox->setVisible(false); - } - else if (chat.mFromName.empty() - || mSourceType == CHAT_SOURCE_SYSTEM) - { - mFrom = LLTrans::getString("SECOND_LIFE"); - if(!chat.mFromName.empty() && (mFrom != chat.mFromName)) - { - mFrom += " (" + chat.mFromName + ")"; - } - user_name->setValue(mFrom); - updateMinUserNameWidth(); - } - else if (mSourceType == CHAT_SOURCE_AGENT - && !mAvatarID.isNull() - && chat.mChatStyle != CHAT_STYLE_HISTORY) - { - // ...from a normal user, lookup the name and fill in later. - // *NOTE: Do not do this for chat history logs, otherwise the viewer - // will flood the People API with lookup requests on startup - - // Start with blank so sample data from XUI XML doesn't - // flash on the screen - user_name->setValue( LLSD() ); - fetchAvatarName(); - } - else if (chat.mChatStyle == CHAT_STYLE_HISTORY || - mSourceType == CHAT_SOURCE_AGENT) - { - //if it's an avatar name with a username add formatting - S32 username_start = chat.mFromName.rfind(" ("); - S32 username_end = chat.mFromName.rfind(')'); - - if (username_start != std::string::npos && - username_end == (chat.mFromName.length() - 1)) - { - mFrom = chat.mFromName.substr(0, username_start); - user_name->setValue(mFrom); - - if (gSavedSettings.getBOOL("NameTagShowUsernames")) - { - std::string username = chat.mFromName.substr(username_start + 2); - username = username.substr(0, username.length() - 1); - LLStyle::Params style_params_name; - LLColor4 userNameColor = LLUIColorTable::instance().getColor("EmphasisColor"); - style_params_name.color(userNameColor); - style_params_name.font.name("SansSerifSmall"); - style_params_name.font.style("NORMAL"); - style_params_name.readonly_color(userNameColor); - user_name->appendText(" - " + username, false, style_params_name); - } - } - else - { - mFrom = chat.mFromName; - user_name->setValue(mFrom); - updateMinUserNameWidth(); - } - } - else - { - // ...from an object, just use name as given - mFrom = chat.mFromName; - user_name->setValue(mFrom); - updateMinUserNameWidth(); - } - - - setTimeField(chat); - - // Set up the icon. - LLAvatarIconCtrl* icon = getChild<LLAvatarIconCtrl>("avatar_icon"); - - if(mSourceType != CHAT_SOURCE_AGENT || mAvatarID.isNull()) - icon->setDrawTooltip(false); - - switch (mSourceType) - { - case CHAT_SOURCE_AGENT: - icon->setValue(chat.mFromID); - break; - case CHAT_SOURCE_OBJECT: - icon->setValue(LLSD("OBJECT_Icon")); - break; - case CHAT_SOURCE_SYSTEM: - case CHAT_SOURCE_REGION: - icon->setValue(LLSD("SL_Logo")); - break; - case CHAT_SOURCE_TELEPORT: - icon->setValue(LLSD("Command_Destinations_Icon")); - break; - case CHAT_SOURCE_UNKNOWN: - icon->setValue(LLSD("Unknown_Icon")); - } - - // In case the message came from an object, save the object info - // to be able properly show its profile. - if ( chat.mSourceType == CHAT_SOURCE_OBJECT) - { - std::string slurl = args["slurl"].asString(); - if (slurl.empty() && LLWorld::instanceExists()) - { - LLViewerRegion *region = LLWorld::getInstance()->getRegionFromPosAgent(chat.mPosAgent); - if(region) - { - LLSLURL region_slurl(region->getName(), chat.mPosAgent); - slurl = region_slurl.getLocationString(); - } - } - - LLSD payload; - payload["object_id"] = chat.mFromID; - payload["name"] = chat.mFromName; - payload["owner_id"] = chat.mOwnerID; - payload["slurl"] = LLWeb::escapeURL(slurl); - - mObjectData = payload; - } - } - - /*virtual*/ void draw() - { - LLTextBox* user_name = mUserNameTextBox; //getChild<LLTextBox>("user_name"); - LLTextBox* time_box = mTimeBoxTextBox; //getChild<LLTextBox>("time_box"); - - LLRect user_name_rect = user_name->getRect(); - S32 user_name_width = user_name_rect.getWidth(); - S32 time_box_width = time_box->getRect().getWidth(); - - if (mNeedsTimeBox && !time_box->getVisible() && user_name_width > mMinUserNameWidth) - { - user_name_rect.mRight -= time_box_width; - user_name->reshape(user_name_rect.getWidth(), user_name_rect.getHeight()); - user_name->setRect(user_name_rect); - - time_box->setVisible(true); - } - - LLPanel::draw(); - } - - void updateMinUserNameWidth() - { - if (mUserNameFont) - { - LLTextBox* user_name = getChild<LLTextBox>("user_name"); - const LLWString& text = user_name->getWText(); - mMinUserNameWidth = mUserNameFont->getWidth(text.c_str()) + PADDING; - } - } - -protected: - static const S32 PADDING = 20; - - void showContextMenu(S32 x,S32 y) - { - if(mSourceType == CHAT_SOURCE_SYSTEM) - showSystemContextMenu(x,y); - if(mAvatarID.notNull() && mSourceType == CHAT_SOURCE_AGENT) - showAvatarContextMenu(x,y); - if(mAvatarID.notNull() && mSourceType == CHAT_SOURCE_OBJECT) - showObjectContextMenu(x,y); - } - - void showSystemContextMenu(S32 x,S32 y) - { - } - - void showObjectContextMenu(S32 x,S32 y) - { - LLMenuGL* menu = (LLMenuGL*)mPopupMenuHandleObject.get(); - if (!menu) - { - LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; - LLUICtrl::EnableCallbackRegistry::ScopedRegistrar registrar_enable; - registrar.add("ObjectIcon.Action", boost::bind(&LLChatHistoryHeader::onObjectIconContextMenuItemClicked, this, _2)); - registrar_enable.add("ObjectIcon.Visible", boost::bind(&LLChatHistoryHeader::onObjectIconContextMenuItemVisible, this, _2)); - - menu = LLUICtrlFactory::getInstance()->createFromFile<LLMenuGL>("menu_object_icon.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); - if (menu) - { - mPopupMenuHandleObject = menu->getHandle(); - menu->updateParent(LLMenuGL::sMenuContainer); - LLMenuGL::showPopup(this, menu, x, y); - } - else - { - LL_WARNS() << " Failed to create menu_object_icon.xml" << LL_ENDL; - } - } - else - { - LLMenuGL::showPopup(this, menu, x, y); - } - } - - void showAvatarContextMenu(S32 x,S32 y) - { - LLMenuGL* menu = (LLMenuGL*)mPopupMenuHandleAvatar.get(); - if (!menu) - { - LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; - LLUICtrl::EnableCallbackRegistry::ScopedRegistrar registrar_enable; - registrar.add("AvatarIcon.Action", boost::bind(&LLChatHistoryHeader::onAvatarIconContextMenuItemClicked, this, _2)); - registrar_enable.add("AvatarIcon.Check", boost::bind(&LLChatHistoryHeader::onAvatarIconContextMenuItemChecked, this, _2)); - registrar_enable.add("AvatarIcon.Enable", boost::bind(&LLChatHistoryHeader::onAvatarIconContextMenuItemEnabled, this, _2)); - registrar_enable.add("AvatarIcon.Visible", boost::bind(&LLChatHistoryHeader::onAvatarIconContextMenuItemVisible, this, _2)); - - menu = LLUICtrlFactory::getInstance()->createFromFile<LLMenuGL>("menu_avatar_icon.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); - if (menu) - { - mPopupMenuHandleAvatar = menu->getHandle(); - } - else - { - LL_WARNS() << " Failed to create menu_avatar_icon.xml" << LL_ENDL; - } - } - - if(menu) - { - bool is_friend = LLAvatarActions::isFriend(mAvatarID); - bool is_group_session = gAgent.isInGroup(mSessionID); - - menu->setItemEnabled("Add Friend", !is_friend); - menu->setItemEnabled("Remove Friend", is_friend); - menu->setItemVisible("Moderator Options Separator", is_group_session && isGroupModerator()); - menu->setItemVisible("Moderator Options", is_group_session && isGroupModerator()); - menu->setItemVisible("Group Ban Separator", is_group_session && canBanInGroup()); - menu->setItemVisible("BanMember", is_group_session && canBanInGroup()); - - if(gAgentID == mAvatarID) - { - menu->setItemEnabled("Add Friend", false); - menu->setItemEnabled("Send IM", false); - menu->setItemEnabled("Remove Friend", false); - menu->setItemEnabled("Offer Teleport",false); - menu->setItemEnabled("Request Teleport",false); - menu->setItemEnabled("Voice Call", false); - menu->setItemEnabled("Chat History", false); - menu->setItemEnabled("Invite Group", false); - menu->setItemEnabled("Zoom In", false); - menu->setItemEnabled("Share", false); - menu->setItemEnabled("Pay", false); - menu->setItemEnabled("Block Unblock", false); - menu->setItemEnabled("Mute Text", false); - } - else - { - LLUUID currentSessionID = LLIMMgr::computeSessionID(IM_NOTHING_SPECIAL, mAvatarID); - if (mSessionID == currentSessionID) - { - menu->setItemVisible("Send IM", false); - } - menu->setItemEnabled("Offer Teleport", LLAvatarActions::canOfferTeleport(mAvatarID)); - menu->setItemEnabled("Request Teleport", LLAvatarActions::canOfferTeleport(mAvatarID)); - menu->setItemEnabled("Voice Call", LLAvatarActions::canCall()); - - // We should only show 'Zoom in' item in a nearby chat - bool should_show_zoom = !LLIMModel::getInstance()->findIMSession(currentSessionID); - menu->setItemVisible("Zoom In", should_show_zoom && gObjectList.findObject(mAvatarID)); - menu->setItemEnabled("Block Unblock", LLAvatarActions::canBlock(mAvatarID)); - menu->setItemEnabled("Mute Text", LLAvatarActions::canBlock(mAvatarID)); - menu->setItemEnabled("Chat History", LLLogChat::isTranscriptExist(mAvatarID)); - } - - menu->setItemEnabled("Map", (LLAvatarTracker::instance().isBuddyOnline(mAvatarID) && is_agent_mappable(mAvatarID)) || gAgent.isGodlike() ); - menu->buildDrawLabels(); - menu->updateParent(LLMenuGL::sMenuContainer); - LLMenuGL::showPopup(this, menu, x, y); - } - } - - void showInfoCtrl() - { - const bool isVisible = !mAvatarID.isNull() && !mFrom.empty() && CHAT_SOURCE_SYSTEM != mSourceType && CHAT_SOURCE_REGION != mSourceType; - if (isVisible) - { - const LLRect sticky_rect = mUserNameTextBox->getRect(); - S32 icon_x = llmin(sticky_rect.mLeft + mUserNameTextBox->getTextBoundingRect().getWidth() + 7, sticky_rect.mRight - 3); - mInfoCtrl->setOrigin(icon_x, sticky_rect.getCenterY() - mInfoCtrl->getRect().getHeight() / 2 ) ; - } - mInfoCtrl->setVisible(isVisible); - } - - void hideInfoCtrl() - { - mInfoCtrl->setVisible(false); - } - -private: - void setTimeField(const LLChat& chat) - { - LLTextBox* time_box = getChild<LLTextBox>("time_box"); - - LLRect rect_before = time_box->getRect(); - - time_box->setValue(chat.mTimeStr); - - // set necessary textbox width to fit all text - time_box->reshapeToFitText(); - LLRect rect_after = time_box->getRect(); - - // move rect to the left to correct position... - S32 delta_pos_x = rect_before.getWidth() - rect_after.getWidth(); - S32 delta_pos_y = rect_before.getHeight() - rect_after.getHeight(); - time_box->translate(delta_pos_x, delta_pos_y); - - //... & change width of the name control - LLView* user_name = getChild<LLView>("user_name"); - const LLRect& user_rect = user_name->getRect(); - user_name->reshape(user_rect.getWidth() + delta_pos_x, user_rect.getHeight()); - } - - void fetchAvatarName() - { - if (mAvatarID.notNull()) - { - if (mAvatarNameCacheConnection.connected()) - { - mAvatarNameCacheConnection.disconnect(); - } - mAvatarNameCacheConnection = LLAvatarNameCache::get(mAvatarID, - boost::bind(&LLChatHistoryHeader::onAvatarNameCache, this, _1, _2)); - } - } - - void onAvatarNameCache(const LLUUID& agent_id, const LLAvatarName& av_name) - { - mAvatarNameCacheConnection.disconnect(); - - mFrom = av_name.getDisplayName(); - - LLTextBox* user_name = getChild<LLTextBox>("user_name"); - user_name->setValue( LLSD(av_name.getDisplayName() ) ); - user_name->setToolTip( av_name.getUserName() ); - - if (gSavedSettings.getBOOL("NameTagShowUsernames") && - av_name.useDisplayNames() && - !av_name.isDisplayNameDefault()) - { - LLStyle::Params style_params_name; - LLColor4 userNameColor = LLUIColorTable::instance().getColor("EmphasisColor"); - style_params_name.color(userNameColor); - style_params_name.font.name("SansSerifSmall"); - style_params_name.font.style("NORMAL"); - style_params_name.readonly_color(userNameColor); - user_name->appendText(" - " + av_name.getUserName(), false, style_params_name); - } - setToolTip( av_name.getUserName() ); - // name might have changed, update width - updateMinUserNameWidth(); - } - -protected: - LLHandle<LLView> mPopupMenuHandleAvatar; - LLHandle<LLView> mPopupMenuHandleObject; - - LLUICtrl* mInfoCtrl; - - LLUUID mAvatarID; - LLSD mObjectData; - EChatSourceType mSourceType; - std::string mFrom; - LLUUID mSessionID; - std::string mText; - F64 mTime; // IM's frame time - time_t mCreationTime; // Views's time - - S32 mMinUserNameWidth; - const LLFontGL* mUserNameFont; - LLTextBox* mUserNameTextBox; - LLTextBox* mTimeBoxTextBox; - - bool mNeedsTimeBox; - -private: - boost::signals2::connection mAvatarNameCacheConnection; -}; - -LLChatHistory::LLChatHistory(const LLChatHistory::Params& p) -: LLUICtrl(p), - mMessageHeaderFilename(p.message_header), - mMessageSeparatorFilename(p.message_separator), - mLeftTextPad(p.left_text_pad), - mRightTextPad(p.right_text_pad), - mLeftWidgetPad(p.left_widget_pad), - mRightWidgetPad(p.right_widget_pad), - mTopSeparatorPad(p.top_separator_pad), - mBottomSeparatorPad(p.bottom_separator_pad), - mTopHeaderPad(p.top_header_pad), - mBottomHeaderPad(p.bottom_header_pad), - mIsLastMessageFromLog(false), - mNotifyAboutUnreadMsg(p.notify_unread_msg) -{ - LLTextEditor::Params editor_params(p); - editor_params.rect = getLocalRect(); - editor_params.follows.flags = FOLLOWS_ALL; - editor_params.enabled = false; // read only - editor_params.show_context_menu = "true"; - editor_params.trusted_content = false; - editor_params.text_valign = LLFontGL::VAlign::VCENTER; - editor_params.use_color = true; - mEditor = LLUICtrlFactory::create<LLTextEditor>(editor_params, this); - mEditor->setIsFriendCallback(LLAvatarActions::isFriend); - mEditor->setIsObjectBlockedCallback(boost::bind(&LLMuteList::isMuted, LLMuteList::getInstance(), _1, _2, 0)); - -} - -LLSD LLChatHistory::getValue() const -{ - return LLSD(mEditor->getText()); -} - -LLChatHistory::~LLChatHistory() -{ - this->clear(); -} - -void LLChatHistory::initFromParams(const LLChatHistory::Params& p) -{ - static LLUICachedControl<S32> scrollbar_size ("UIScrollbarSize", 0); - - LLRect stack_rect = getLocalRect(); - stack_rect.mRight -= scrollbar_size; - LLLayoutStack::Params layout_p; - layout_p.rect = stack_rect; - layout_p.follows.flags = FOLLOWS_ALL; - layout_p.orientation = LLLayoutStack::VERTICAL; - layout_p.mouse_opaque = false; - - LLLayoutStack* stackp = LLUICtrlFactory::create<LLLayoutStack>(layout_p, this); - - const S32 NEW_TEXT_NOTICE_HEIGHT = 20; - - LLLayoutPanel::Params panel_p; - panel_p.name = "spacer"; - panel_p.background_visible = false; - panel_p.has_border = false; - panel_p.mouse_opaque = false; - panel_p.min_dim = 30; - panel_p.auto_resize = true; - panel_p.user_resize = false; - - stackp->addPanel(LLUICtrlFactory::create<LLLayoutPanel>(panel_p), LLLayoutStack::ANIMATE); - - panel_p.name = "new_text_notice_holder"; - LLRect new_text_notice_rect = getLocalRect(); - new_text_notice_rect.mTop = new_text_notice_rect.mBottom + NEW_TEXT_NOTICE_HEIGHT; - panel_p.rect = new_text_notice_rect; - panel_p.background_opaque = true; - panel_p.background_visible = true; - panel_p.visible = false; - panel_p.min_dim = 0; - panel_p.auto_resize = false; - panel_p.user_resize = false; - mMoreChatPanel = LLUICtrlFactory::create<LLLayoutPanel>(panel_p); - - LLTextBox::Params text_p(p.more_chat_text); - text_p.rect = mMoreChatPanel->getLocalRect(); - text_p.follows.flags = FOLLOWS_ALL; - text_p.name = "more_chat_text"; - mMoreChatText = LLUICtrlFactory::create<LLTextBox>(text_p, mMoreChatPanel); - mMoreChatText->setClickedCallback(boost::bind(&LLChatHistory::onClickMoreText, this)); - - stackp->addPanel(mMoreChatPanel, LLLayoutStack::ANIMATE); -} - - -/*void LLChatHistory::updateTextRect() -{ - static LLUICachedControl<S32> texteditor_border ("UITextEditorBorder", 0); - - LLRect old_text_rect = mVisibleTextRect; - mVisibleTextRect = mScroller->getContentWindowRect(); - mVisibleTextRect.stretch(-texteditor_border); - mVisibleTextRect.mLeft += mLeftTextPad; - mVisibleTextRect.mRight -= mRightTextPad; - if (mVisibleTextRect != old_text_rect) - { - needsReflow(); - } -}*/ - -LLView* LLChatHistory::getSeparator() -{ - LLPanel* separator = LLUICtrlFactory::getInstance()->createFromFile<LLPanel>(mMessageSeparatorFilename, NULL, LLPanel::child_registry_t::instance()); - return separator; -} - -LLView* LLChatHistory::getHeader(const LLChat& chat,const LLStyle::Params& style_params, const LLSD& args) -{ - LLChatHistoryHeader* header = LLChatHistoryHeader::createInstance(mMessageHeaderFilename); - if (header) - header->setup(chat, style_params, args); - return header; -} - -void LLChatHistory::onClickMoreText() -{ - mEditor->endOfDoc(); -} - -void LLChatHistory::clear() -{ - mLastFromName.clear(); - mEditor->clear(); - mLastFromID = LLUUID::null; -} - -static LLTrace::BlockTimerStatHandle FTM_APPEND_MESSAGE("Append Chat Message"); - -void LLChatHistory::appendMessage(const LLChat& chat, const LLSD &args, const LLStyle::Params& input_append_params) -{ - LL_RECORD_BLOCK_TIME(FTM_APPEND_MESSAGE); - bool use_plain_text_chat_history = args["use_plain_text_chat_history"].asBoolean(); - bool square_brackets = false; // square brackets necessary for a system messages - - llassert(mEditor); - if (!mEditor) - return; - - bool from_me = chat.mFromID == gAgent.getID(); - mEditor->setPlainText(use_plain_text_chat_history); - - if (mNotifyAboutUnreadMsg && !mEditor->scrolledToEnd() && !from_me && !chat.mFromName.empty()) - { - mUnreadChatSources.insert(chat.mFromName); - mMoreChatPanel->setVisible(true); - std::string chatters; - for (const std::string& source : mUnreadChatSources) - { - chatters += chatters.size() ? ", " + source : source; - } - LLStringUtil::format_map_t args; - args["SOURCES"] = chatters; - - std::string xml_desc = mUnreadChatSources.size() == 1 ? - "unread_chat_single" : "unread_chat_multiple"; - mMoreChatText->setValue(LLTrans::getString(xml_desc, args)); - S32 height = mMoreChatText->getTextPixelHeight() + 5; - mMoreChatPanel->reshape(mMoreChatPanel->getRect().getWidth(), height); - } - - LLColor4 txt_color = LLUIColorTable::instance().getColor("White"); - LLColor4 name_color(txt_color); - - LLViewerChat::getChatColor(chat,txt_color); - LLFontGL* fontp = LLViewerChat::getChatFont(); - std::string font_name = LLFontGL::nameFromFont(fontp); - std::string font_size = LLFontGL::sizeFromFont(fontp); - - LLStyle::Params body_message_params; - body_message_params.color(txt_color); - body_message_params.readonly_color(txt_color); - body_message_params.font.name(font_name); - body_message_params.font.size(font_size); - body_message_params.font.style(input_append_params.font.style); - - LLStyle::Params name_params(body_message_params); - name_params.color(name_color); - name_params.readonly_color(name_color); - - std::string prefix = chat.mText.substr(0, 4); - - //IRC styled /me messages. - bool irc_me = prefix == "/me " || prefix == "/me'"; - - // Delimiter after a name in header copy/past and in plain text mode - std::string delimiter = ": "; - std::string shout = LLTrans::getString("shout"); - std::string whisper = LLTrans::getString("whisper"); - if (chat.mChatType == CHAT_TYPE_SHOUT || - chat.mChatType == CHAT_TYPE_WHISPER || - chat.mText.compare(0, shout.length(), shout) == 0 || - chat.mText.compare(0, whisper.length(), whisper) == 0) - { - delimiter = " "; - } - - // Don't add any delimiter after name in irc styled messages - if (irc_me || chat.mChatStyle == CHAT_STYLE_IRC) - { - delimiter = LLStringUtil::null; - body_message_params.font.style = "ITALIC"; - } - - if (chat.mChatType == CHAT_TYPE_WHISPER) - { - body_message_params.font.style = "ITALIC"; - } - else if (chat.mChatType == CHAT_TYPE_SHOUT) - { - body_message_params.font.style = "BOLD"; - } - - bool message_from_log = chat.mChatStyle == CHAT_STYLE_HISTORY; - bool teleport_separator = chat.mSourceType == CHAT_SOURCE_TELEPORT; - // We graying out chat history by graying out messages that contains full date in a time string - if (message_from_log) - { - txt_color = LLColor4::grey; - body_message_params.color(txt_color); - body_message_params.readonly_color(txt_color); - name_params.color(txt_color); - name_params.readonly_color(txt_color); - } - - bool prependNewLineState = mEditor->getText().size() != 0; - - // compact mode: show a timestamp and name - if (use_plain_text_chat_history) - { - square_brackets = chat.mSourceType == CHAT_SOURCE_SYSTEM; - - LLStyle::Params timestamp_style(body_message_params); - - // out of the timestamp - if (args["show_time"].asBoolean() && !teleport_separator) - { - if (!message_from_log) - { - LLColor4 timestamp_color = LLUIColorTable::instance().getColor("ChatTimestampColor"); - timestamp_style.color(timestamp_color); - timestamp_style.readonly_color(timestamp_color); - } - mEditor->appendText("[" + chat.mTimeStr + "] ", prependNewLineState, timestamp_style); - prependNewLineState = false; - } - - // out the opening square bracket (if need) - if (square_brackets) - { - mEditor->appendText("[", prependNewLineState, body_message_params); - prependNewLineState = false; - } - - // names showing - if (args["show_names_for_p2p_conv"].asBoolean() && utf8str_trim(chat.mFromName).size()) - { - // Don't hotlink any messages from the system (e.g. "Second Life:"), so just add those in plain text. - if (chat.mSourceType == CHAT_SOURCE_OBJECT && chat.mFromID.notNull()) - { - // for object IMs, create a secondlife:///app/objectim SLapp - std::string url = LLViewerChat::getSenderSLURL(chat, args); - - // set the link for the object name to be the objectim SLapp - // (don't let object names with hyperlinks override our objectim Url) - LLStyle::Params link_params(body_message_params); - LLColor4 link_color = LLUIColorTable::instance().getColor("HTMLLinkColor"); - link_params.color = link_color; - link_params.readonly_color = link_color; - link_params.is_link = true; - link_params.link_href = url; - - mEditor->appendText(chat.mFromName + delimiter, prependNewLineState, link_params); - prependNewLineState = false; - } - else if ( chat.mFromName != SYSTEM_FROM && chat.mFromID.notNull() && !message_from_log && chat.mSourceType != CHAT_SOURCE_REGION) - { - LLStyle::Params link_params(body_message_params); - link_params.overwriteFrom(LLStyleMap::instance().lookupAgent(chat.mFromID)); - - // Add link to avatar's inspector and delimiter to message. - mEditor->appendText(std::string(link_params.link_href) + delimiter, - prependNewLineState, link_params); - prependNewLineState = false; - } - else if (teleport_separator) - { - std::string tp_text = LLTrans::getString("teleport_preamble_compact_chat"); - mEditor->appendText(tp_text + " <nolink>" + chat.mFromName + "</nolink>", - prependNewLineState, body_message_params); - prependNewLineState = false; - } - else - { - mEditor->appendText("<nolink>" + chat.mFromName + "</nolink>" + delimiter, - prependNewLineState, body_message_params); - prependNewLineState = false; - } - } - } - else // showing timestamp and name in the expanded mode - { - prependNewLineState = false; - LLView* view = NULL; - LLInlineViewSegment::Params p; - p.force_newline = true; - p.left_pad = mLeftWidgetPad; - p.right_pad = mRightWidgetPad; - - LLDate new_message_time = LLDate::now(); - if (!teleport_separator - && mLastFromName == chat.mFromName - && mLastFromID == chat.mFromID - && mLastMessageTime.notNull() - && (new_message_time.secondsSinceEpoch() - mLastMessageTime.secondsSinceEpoch()) < 60.0 - && mIsLastMessageFromLog == message_from_log) //distinguish between current and previous chat session's histories - { - view = getSeparator(); - if (!view) - { - // Might be wiser to make this LL_ERRS, getSeparator() should work in case of correct instalation. - LL_WARNS() << "Failed to create separator from " << mMessageSeparatorFilename << ": can't append to history" << LL_ENDL; - return; - } - - p.top_pad = mTopSeparatorPad; - p.bottom_pad = mBottomSeparatorPad; - } - else - { - view = getHeader(chat, name_params, args); - if (!view) - { - LL_WARNS() << "Failed to create header from " << mMessageHeaderFilename << ": can't append to history" << LL_ENDL; - return; - } - - p.top_pad = mEditor->getLength() ? mTopHeaderPad : 0; - p.bottom_pad = teleport_separator ? mBottomSeparatorPad : mBottomHeaderPad; - } - p.view = view; - - //Prepare the rect for the view - LLRect target_rect = mEditor->getDocumentView()->getRect(); - // squeeze down the widget by subtracting padding off left and right - target_rect.mLeft += mLeftWidgetPad + mEditor->getHPad(); - target_rect.mRight -= mRightWidgetPad; - view->reshape(target_rect.getWidth(), view->getRect().getHeight()); - view->setOrigin(target_rect.mLeft, view->getRect().mBottom); - - std::string widget_associated_text = "\n[" + chat.mTimeStr + "] "; - if (utf8str_trim(chat.mFromName).size() != 0 && chat.mFromName != SYSTEM_FROM) - widget_associated_text += chat.mFromName + delimiter; - - mEditor->appendWidget(p, widget_associated_text, false); - mLastFromName = chat.mFromName; - mLastFromID = chat.mFromID; - mLastMessageTime = new_message_time; - mIsLastMessageFromLog = message_from_log; - } - - // body of the message processing - - // notify processing - if (chat.mNotifId.notNull()) - { - LLNotificationPtr notification = LLNotificationsUtil::find(chat.mNotifId); - if (notification != NULL) - { - bool create_toast = true; - if (notification->getName() == "OfferFriendship") - { - // We don't want multiple friendship offers to appear, this code checks if there are previous offers - // by iterating though all panels. - // Note: it might be better to simply add a "pending offer" flag somewhere - for (auto& panel : LLToastNotifyPanel::instance_snapshot()) - { - LLIMToastNotifyPanel * imtoastp = dynamic_cast<LLIMToastNotifyPanel *>(&panel); - const std::string& notification_name = panel.getNotificationName(); - if (notification_name == "OfferFriendship" - && panel.isControlPanelEnabled() - && imtoastp) - { - create_toast = false; - break; - } - } - } - - if (create_toast) - { - LLIMToastNotifyPanel* notify_box = new LLIMToastNotifyPanel( - notification, chat.mSessionID, LLRect::null, !use_plain_text_chat_history, mEditor); - - //Prepare the rect for the view - LLRect target_rect = mEditor->getDocumentView()->getRect(); - // squeeze down the widget by subtracting padding off left and right - target_rect.mLeft += mLeftWidgetPad + mEditor->getHPad(); - target_rect.mRight -= mRightWidgetPad; - notify_box->reshape(target_rect.getWidth(), notify_box->getRect().getHeight()); - notify_box->setOrigin(target_rect.mLeft, notify_box->getRect().mBottom); - - LLInlineViewSegment::Params params; - params.view = notify_box; - params.left_pad = mLeftWidgetPad; - params.right_pad = mRightWidgetPad; - mEditor->appendWidget(params, "\n", false); - } - } - } - // usual messages showing - else if (!teleport_separator) - { - std::string message = irc_me ? chat.mText.substr(3) : chat.mText; - - //MESSAGE TEXT PROCESSING - //*HACK getting rid of redundant sender names in system notifications sent using sender name (see EXT-5010) - if (use_plain_text_chat_history && !from_me && chat.mFromID.notNull()) - { - std::string slurl_about = SLURL_APP_AGENT + chat.mFromID.asString() + SLURL_ABOUT; - if (message.length() > slurl_about.length() && - message.compare(0, slurl_about.length(), slurl_about) == 0) - { - message = message.substr(slurl_about.length(), message.length()-1); - } - } - - if (irc_me && !use_plain_text_chat_history) - { - std::string from_name = chat.mFromName; - LLAvatarName av_name; - if (!chat.mFromID.isNull() && - LLAvatarNameCache::get(chat.mFromID, &av_name) && - !av_name.isDisplayNameDefault()) - { - from_name = av_name.getCompleteName(); - } - message = from_name + message; - } - - if (square_brackets) - { - message += "]"; - } - - mEditor->appendText(message, prependNewLineState, body_message_params); - prependNewLineState = false; - } - - mEditor->blockUndo(); - - // automatically scroll to end when receiving chat from myself - if (from_me) - { - mEditor->setCursorAndScrollToEnd(); - } -} - -void LLChatHistory::draw() -{ - if (mEditor->scrolledToEnd()) - { - mUnreadChatSources.clear(); - mMoreChatPanel->setVisible(false); - } - - LLUICtrl::draw(); -} +/**
+ * @file llchathistory.cpp
+ * @brief LLTextEditor base class
+ *
+ * $LicenseInfo:firstyear=2001&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+ * $/LicenseInfo$
+ */
+
+#include "llviewerprecompiledheaders.h"
+
+#include "llchathistory.h"
+
+#include <boost/signals2.hpp>
+
+#include "llavatarnamecache.h"
+#include "llinstantmessage.h"
+
+#include "llimview.h"
+#include "llcommandhandler.h"
+#include "llpanel.h"
+#include "lluictrlfactory.h"
+#include "llscrollcontainer.h"
+#include "llagent.h"
+#include "llagentdata.h"
+#include "llavataractions.h"
+#include "llavatariconctrl.h"
+#include "llcallingcard.h" //for LLAvatarTracker
+#include "llgroupactions.h"
+#include "llgroupmgr.h"
+#include "llspeakers.h" //for LLIMSpeakerMgr
+#include "lltrans.h"
+#include "llfloaterreg.h"
+#include "llfloaterreporter.h"
+#include "llfloatersidepanelcontainer.h"
+#include "llmutelist.h"
+#include "llstylemap.h"
+#include "llslurl.h"
+#include "lllayoutstack.h"
+#include "llnotifications.h"
+#include "llnotificationsutil.h"
+#include "lltoastnotifypanel.h"
+#include "lltooltip.h"
+#include "llviewerregion.h"
+#include "llviewertexteditor.h"
+#include "llworld.h"
+#include "lluiconstants.h"
+#include "llstring.h"
+#include "llurlaction.h"
+#include "llviewercontrol.h"
+#include "llviewermenu.h"
+#include "llviewerobjectlist.h"
+
+static LLDefaultChildRegistry::Register<LLChatHistory> r("chat_history");
+
+const static std::string NEW_LINE(rawstr_to_utf8("\n"));
+
+const static std::string SLURL_APP_AGENT = "secondlife:///app/agent/";
+const static std::string SLURL_ABOUT = "/about";
+
+// support for secondlife:///app/objectim/{UUID}/ SLapps
+class LLObjectIMHandler : public LLCommandHandler
+{
+public:
+ // requests will be throttled from a non-trusted browser
+ LLObjectIMHandler() : LLCommandHandler("objectim", UNTRUSTED_THROTTLE) {}
+
+ bool handle(const LLSD& params, const LLSD& query_map, const std::string& grid, LLMediaCtrl* web)
+ {
+ if (params.size() < 1)
+ {
+ return false;
+ }
+
+ LLUUID object_id;
+ if (!object_id.set(params[0], false))
+ {
+ return false;
+ }
+
+ LLSD payload;
+ payload["object_id"] = object_id;
+ payload["owner_id"] = query_map["owner"];
+ payload["name"] = query_map["name"];
+ payload["slurl"] = LLWeb::escapeURL(query_map["slurl"]);
+ payload["group_owned"] = query_map["groupowned"];
+ LLFloaterReg::showInstance("inspect_remote_object", payload);
+ return true;
+ }
+};
+LLObjectIMHandler gObjectIMHandler;
+
+class LLChatHistoryHeader: public LLPanel
+{
+public:
+ LLChatHistoryHeader()
+ : LLPanel(),
+ mInfoCtrl(NULL),
+ mPopupMenuHandleAvatar(),
+ mPopupMenuHandleObject(),
+ mAvatarID(),
+ mSourceType(CHAT_SOURCE_UNKNOWN),
+ mFrom(),
+ mSessionID(),
+ mCreationTime(time_corrected()),
+ mMinUserNameWidth(0),
+ mUserNameFont(NULL),
+ mUserNameTextBox(NULL),
+ mTimeBoxTextBox(NULL),
+ mNeedsTimeBox(true),
+ mAvatarNameCacheConnection()
+ {}
+
+ static LLChatHistoryHeader* createInstance(const std::string& file_name)
+ {
+ LLChatHistoryHeader* pInstance = new LLChatHistoryHeader;
+ pInstance->buildFromFile(file_name);
+ return pInstance;
+ }
+
+ ~LLChatHistoryHeader()
+ {
+ if (mAvatarNameCacheConnection.connected())
+ {
+ mAvatarNameCacheConnection.disconnect();
+ }
+ auto menu = mPopupMenuHandleAvatar.get();
+ if (menu)
+ {
+ menu->die();
+ mPopupMenuHandleAvatar.markDead();
+ }
+ menu = mPopupMenuHandleObject.get();
+ if (menu)
+ {
+ menu->die();
+ mPopupMenuHandleObject.markDead();
+ }
+ }
+
+ bool handleMouseUp(S32 x, S32 y, MASK mask)
+ {
+ return LLPanel::handleMouseUp(x,y,mask);
+ }
+
+ void onObjectIconContextMenuItemClicked(const LLSD& userdata)
+ {
+ std::string level = userdata.asString();
+
+ if (level == "profile")
+ {
+ LLFloaterReg::showInstance("inspect_remote_object", mObjectData);
+ }
+ else if (level == "block")
+ {
+ LLMuteList::getInstance()->add(LLMute(getAvatarId(), mFrom, LLMute::OBJECT));
+
+ LLFloaterSidePanelContainer::showPanel("people", "panel_people",
+ LLSD().with("people_panel_tab_name", "blocked_panel").with("blocked_to_select", getAvatarId()));
+ }
+ else if (level == "unblock")
+ {
+ LLMuteList::getInstance()->remove(LLMute(getAvatarId(), mFrom, LLMute::OBJECT));
+ }
+ else if (level == "map")
+ {
+ std::string url = "secondlife://" + mObjectData["slurl"].asString();
+ LLUrlAction::showLocationOnMap(url);
+ }
+ else if (level == "teleport")
+ {
+ std::string url = "secondlife://" + mObjectData["slurl"].asString();
+ LLUrlAction::teleportToLocation(url);
+ }
+
+ }
+
+ bool onObjectIconContextMenuItemVisible(const LLSD& userdata)
+ {
+ std::string level = userdata.asString();
+ if (level == "is_blocked")
+ {
+ return LLMuteList::getInstance()->isMuted(getAvatarId(), mFrom, LLMute::flagTextChat);
+ }
+ else if (level == "not_blocked")
+ {
+ return !LLMuteList::getInstance()->isMuted(getAvatarId(), mFrom, LLMute::flagTextChat);
+ }
+ return false;
+ }
+
+ void banGroupMember(const LLUUID& participant_uuid)
+ {
+ LLUUID group_uuid = mSessionID;
+ LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(group_uuid);
+ if (!gdatap)
+ {
+ // Not a group
+ return;
+ }
+
+ gdatap->banMemberById(participant_uuid);
+ }
+
+ bool canBanInGroup()
+ {
+ LLUUID group_uuid = mSessionID;
+ LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(group_uuid);
+ if (!gdatap)
+ {
+ // Not a group
+ return false;
+ }
+
+ if (gAgent.hasPowerInGroup(group_uuid, GP_ROLE_REMOVE_MEMBER)
+ && gAgent.hasPowerInGroup(group_uuid, GP_GROUP_BAN_ACCESS))
+ {
+ return true;
+ }
+
+ return false;
+ }
+
+ bool canBanGroupMember(const LLUUID& participant_uuid)
+ {
+ LLUUID group_uuid = mSessionID;
+ LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(group_uuid);
+ if (!gdatap)
+ {
+ // Not a group
+ return false;
+ }
+
+ if (gdatap->mPendingBanRequest)
+ {
+ return false;
+ }
+
+ if (gAgentID == getAvatarId())
+ {
+ //Don't ban self
+ return false;
+ }
+
+ if (gdatap->isRoleMemberDataComplete())
+ {
+ if (gdatap->mMembers.size())
+ {
+ LLGroupMgrGroupData::member_list_t::iterator mi = gdatap->mMembers.find(participant_uuid);
+ if (mi != gdatap->mMembers.end())
+ {
+ LLGroupMemberData* member_data = (*mi).second;
+ // Is the member an owner?
+ if (member_data && member_data->isInRole(gdatap->mOwnerRole))
+ {
+ return false;
+ }
+
+ if (gAgent.hasPowerInGroup(group_uuid, GP_ROLE_REMOVE_MEMBER)
+ && gAgent.hasPowerInGroup(group_uuid, GP_GROUP_BAN_ACCESS))
+ {
+ return true;
+ }
+ }
+ }
+ }
+
+ LLSpeakerMgr * speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(mSessionID);
+ if (speaker_mgr)
+ {
+ LLSpeaker * speakerp = speaker_mgr->findSpeaker(participant_uuid).get();
+
+ if (speakerp
+ && gAgent.hasPowerInGroup(group_uuid, GP_ROLE_REMOVE_MEMBER)
+ && gAgent.hasPowerInGroup(group_uuid, GP_GROUP_BAN_ACCESS))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ bool isGroupModerator()
+ {
+ LLSpeakerMgr * speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(mSessionID);
+ if (!speaker_mgr)
+ {
+ LL_WARNS() << "Speaker manager is missing" << LL_ENDL;
+ return false;
+ }
+
+ // Is session a group call/chat?
+ if(gAgent.isInGroup(mSessionID))
+ {
+ LLSpeaker * speakerp = speaker_mgr->findSpeaker(gAgentID).get();
+
+ // Is agent a moderator?
+ return speakerp && speakerp->mIsModerator;
+ }
+
+ return false;
+ }
+
+ bool canModerate(const std::string& userdata)
+ {
+ // only group moderators can perform actions related to this "enable callback"
+ if (!isGroupModerator() || gAgentID == getAvatarId())
+ {
+ return false;
+ }
+
+ LLSpeakerMgr * speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(mSessionID);
+ if (!speaker_mgr)
+ {
+ return false;
+ }
+
+ LLSpeaker * speakerp = speaker_mgr->findSpeaker(getAvatarId()).get();
+ if (!speakerp)
+ {
+ return false;
+ }
+
+ bool voice_channel = speakerp->isInVoiceChannel();
+
+ if ("can_moderate_voice" == userdata)
+ {
+ return voice_channel;
+ }
+ else if ("can_mute" == userdata)
+ {
+ return voice_channel && (speakerp->mStatus != LLSpeaker::STATUS_MUTED);
+ }
+ else if ("can_unmute" == userdata)
+ {
+ return speakerp->mStatus == LLSpeaker::STATUS_MUTED;
+ }
+ else if ("can_allow_text_chat" == userdata)
+ {
+ return true;
+ }
+
+ return false;
+ }
+
+ void onAvatarIconContextMenuItemClicked(const LLSD& userdata)
+ {
+ std::string level = userdata.asString();
+
+ if (level == "profile")
+ {
+ LLAvatarActions::showProfile(getAvatarId());
+ }
+ else if (level == "im")
+ {
+ LLAvatarActions::startIM(getAvatarId());
+ }
+ else if (level == "teleport")
+ {
+ LLAvatarActions::offerTeleport(getAvatarId());
+ }
+ else if (level == "request_teleport")
+ {
+ LLAvatarActions::teleportRequest(getAvatarId());
+ }
+ else if (level == "voice_call")
+ {
+ LLAvatarActions::startCall(getAvatarId());
+ }
+ else if (level == "chat_history")
+ {
+ LLAvatarActions::viewChatHistory(getAvatarId());
+ }
+ else if (level == "add")
+ {
+ LLAvatarActions::requestFriendshipDialog(getAvatarId(), mFrom);
+ }
+ else if (level == "remove")
+ {
+ LLAvatarActions::removeFriendDialog(getAvatarId());
+ }
+ else if (level == "invite_to_group")
+ {
+ LLAvatarActions::inviteToGroup(getAvatarId());
+ }
+ else if (level == "zoom_in")
+ {
+ handle_zoom_to_object(getAvatarId());
+ }
+ else if (level == "map")
+ {
+ LLAvatarActions::showOnMap(getAvatarId());
+ }
+ else if (level == "share")
+ {
+ LLAvatarActions::share(getAvatarId());
+ }
+ else if (level == "pay")
+ {
+ LLAvatarActions::pay(getAvatarId());
+ }
+ else if (level == "report_abuse")
+ {
+ std::string time_string;
+ if (mTime > 0) // have frame time
+ {
+ time_t current_time = time_corrected();
+ time_t message_time = current_time - LLFrameTimer::getElapsedSeconds() + mTime;
+
+ time_string = "[" + LLTrans::getString("TimeMonth") + "]/["
+ + LLTrans::getString("TimeDay") + "]/["
+ + LLTrans::getString("TimeYear") + "] ["
+ + LLTrans::getString("TimeHour") + "]:["
+ + LLTrans::getString("TimeMin") + "]";
+
+ LLSD substitution;
+
+ substitution["datetime"] = (S32)message_time;
+ LLStringUtil::format(time_string, substitution);
+ }
+ else
+ {
+ // From history. This might be empty or not full.
+ // See LLChatLogParser::parse
+ time_string = getChild<LLTextBox>("time_box")->getValue().asString();
+
+ // Just add current date if not full.
+ // Should be fine since both times are supposed to be stl
+ if (!time_string.empty() && time_string.size() < 7)
+ {
+ time_string = "[" + LLTrans::getString("TimeMonth") + "]/["
+ + LLTrans::getString("TimeDay") + "]/["
+ + LLTrans::getString("TimeYear") + "] " + time_string;
+
+ LLSD substitution;
+ // To avoid adding today's date to yesterday's timestamp,
+ // use creation time instead of current time
+ substitution["datetime"] = (S32)mCreationTime;
+ LLStringUtil::format(time_string, substitution);
+ }
+ }
+ LLFloaterReporter::showFromChat(mAvatarID, mFrom, time_string, mText);
+ }
+ else if(level == "block_unblock")
+ {
+ LLAvatarActions::toggleMute(getAvatarId(), LLMute::flagVoiceChat);
+ }
+ else if(level == "mute_unmute")
+ {
+ LLAvatarActions::toggleMute(getAvatarId(), LLMute::flagTextChat);
+ }
+ else if(level == "toggle_allow_text_chat")
+ {
+ LLIMSpeakerMgr* speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(mSessionID);
+ speaker_mgr->toggleAllowTextChat(getAvatarId());
+ }
+ else if(level == "group_mute")
+ {
+ LLIMSpeakerMgr* speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(mSessionID);
+ if (speaker_mgr)
+ {
+ speaker_mgr->moderateVoiceParticipant(getAvatarId(), false);
+ }
+ }
+ else if(level == "group_unmute")
+ {
+ LLIMSpeakerMgr* speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(mSessionID);
+ if (speaker_mgr)
+ {
+ speaker_mgr->moderateVoiceParticipant(getAvatarId(), true);
+ }
+ }
+ else if(level == "ban_member")
+ {
+ banGroupMember(getAvatarId());
+ }
+ }
+
+ bool onAvatarIconContextMenuItemChecked(const LLSD& userdata)
+ {
+ std::string level = userdata.asString();
+
+ if (level == "is_blocked")
+ {
+ return LLMuteList::getInstance()->isMuted(getAvatarId(), LLMute::flagVoiceChat);
+ }
+ if (level == "is_muted")
+ {
+ return LLMuteList::getInstance()->isMuted(getAvatarId(), LLMute::flagTextChat);
+ }
+ else if (level == "is_allowed_text_chat")
+ {
+ if (gAgent.isInGroup(mSessionID))
+ {
+ LLIMSpeakerMgr* speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(mSessionID);
+ if(speaker_mgr)
+ {
+ const LLSpeaker * speakerp = speaker_mgr->findSpeaker(getAvatarId());
+ if (NULL != speakerp)
+ {
+ return !speakerp->mModeratorMutedText;
+ }
+ }
+ }
+ return false;
+ }
+ return false;
+ }
+
+ bool onAvatarIconContextMenuItemEnabled(const LLSD& userdata)
+ {
+ std::string level = userdata.asString();
+
+ if (level == "can_allow_text_chat" || level == "can_mute" || level == "can_unmute")
+ {
+ return canModerate(userdata);
+ }
+ else if (level == "report_abuse")
+ {
+ return gAgentID != mAvatarID;
+ }
+ else if (level == "can_ban_member")
+ {
+ return canBanGroupMember(getAvatarId());
+ }
+ return false;
+ }
+
+ bool onAvatarIconContextMenuItemVisible(const LLSD& userdata)
+ {
+ std::string level = userdata.asString();
+
+ if (level == "show_mute")
+ {
+ LLSpeakerMgr * speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(mSessionID);
+ if (speaker_mgr)
+ {
+ LLSpeaker * speakerp = speaker_mgr->findSpeaker(getAvatarId()).get();
+ if (speakerp)
+ {
+ return speakerp->isInVoiceChannel() && speakerp->mStatus != LLSpeaker::STATUS_MUTED;
+ }
+ }
+ return false;
+ }
+ else if (level == "show_unmute")
+ {
+ LLSpeakerMgr * speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(mSessionID);
+ if (speaker_mgr)
+ {
+ LLSpeaker * speakerp = speaker_mgr->findSpeaker(getAvatarId()).get();
+ if (speakerp)
+ {
+ return speakerp->mStatus == LLSpeaker::STATUS_MUTED;
+ }
+ }
+ return false;
+ }
+ return false;
+ }
+
+ bool postBuild()
+ {
+ setDoubleClickCallback(boost::bind(&LLChatHistoryHeader::showInspector, this));
+
+ setMouseEnterCallback(boost::bind(&LLChatHistoryHeader::showInfoCtrl, this));
+ setMouseLeaveCallback(boost::bind(&LLChatHistoryHeader::hideInfoCtrl, this));
+
+ mUserNameTextBox = getChild<LLTextBox>("user_name");
+ mTimeBoxTextBox = getChild<LLTextBox>("time_box");
+
+ mInfoCtrl = LLUICtrlFactory::getInstance()->createFromFile<LLUICtrl>("inspector_info_ctrl.xml", this, LLPanel::child_registry_t::instance());
+ if (mInfoCtrl)
+ {
+ mInfoCtrl->setCommitCallback(boost::bind(&LLChatHistoryHeader::onClickInfoCtrl, mInfoCtrl));
+ mInfoCtrl->setVisible(false);
+ }
+ else
+ {
+ LL_ERRS() << "Failed to create an interface element due to missing or corrupted file inspector_info_ctrl.xml" << LL_ENDL;
+ }
+
+ return LLPanel::postBuild();
+ }
+
+ bool pointInChild(const std::string& name,S32 x,S32 y)
+ {
+ LLUICtrl* child = findChild<LLUICtrl>(name);
+ if(!child)
+ return false;
+
+ LLView* parent = child->getParent();
+ if(parent!=this)
+ {
+ x-=parent->getRect().mLeft;
+ y-=parent->getRect().mBottom;
+ }
+
+ S32 local_x = x - child->getRect().mLeft ;
+ S32 local_y = y - child->getRect().mBottom ;
+ return child->pointInView(local_x, local_y);
+ }
+
+ bool handleRightMouseDown(S32 x, S32 y, MASK mask)
+ {
+ if(pointInChild("avatar_icon",x,y) || pointInChild("user_name",x,y))
+ {
+ showContextMenu(x,y);
+ return true;
+ }
+
+ return LLPanel::handleRightMouseDown(x,y,mask);
+ }
+
+ void showInspector()
+ {
+ if (mAvatarID.isNull() && CHAT_SOURCE_SYSTEM != mSourceType && CHAT_SOURCE_REGION != mSourceType) return;
+
+ if (mSourceType == CHAT_SOURCE_OBJECT)
+ {
+ LLFloaterReg::showInstance("inspect_remote_object", mObjectData);
+ }
+ else if (mSourceType == CHAT_SOURCE_AGENT)
+ {
+ LLFloaterReg::showInstance("inspect_avatar", LLSD().with("avatar_id", mAvatarID));
+ }
+ //if chat source is system, you may add "else" here to define behaviour.
+ }
+
+ static void onClickInfoCtrl(LLUICtrl* info_ctrl)
+ {
+ if (!info_ctrl) return;
+
+ LLChatHistoryHeader* header = dynamic_cast<LLChatHistoryHeader*>(info_ctrl->getParent());
+ if (!header) return;
+
+ header->showInspector();
+ }
+
+
+ const LLUUID& getAvatarId () const { return mAvatarID;}
+
+ void setup(const LLChat& chat, const LLStyle::Params& style_params, const LLSD& args)
+ {
+ mAvatarID = chat.mFromID;
+ mSessionID = chat.mSessionID;
+ mSourceType = chat.mSourceType;
+
+ // To be able to report a message, we need a copy of it's text
+ // and it's easier to store text directly than trying to get
+ // it from a lltextsegment or chat's mEditor
+ mText = chat.mText;
+ mTime = chat.mTime;
+
+ //*TODO overly defensive thing, source type should be maintained out there
+ if((chat.mFromID.isNull() && chat.mFromName.empty()) || (chat.mFromName == SYSTEM_FROM && chat.mFromID.isNull()))
+ {
+ mSourceType = CHAT_SOURCE_SYSTEM;
+ }
+
+ mUserNameFont = style_params.font();
+ if (!mUserNameTextBox)
+ {
+ mUserNameTextBox = getChild<LLTextBox>("user_name");
+ mTimeBoxTextBox = getChild<LLTextBox>("time_box");
+ }
+ LLTextBox* user_name = mUserNameTextBox;
+ user_name->setReadOnlyColor(style_params.readonly_color());
+ user_name->setColor(style_params.color());
+
+ if (mSourceType == CHAT_SOURCE_TELEPORT
+ && chat.mChatStyle == CHAT_STYLE_TELEPORT_SEP)
+ {
+ mFrom = chat.mFromName;
+ mNeedsTimeBox = false;
+ user_name->setValue(mFrom);
+ updateMinUserNameWidth();
+ LLColor4 sep_color = LLUIColorTable::instance().getColor("ChatTeleportSeparatorColor");
+ setTransparentColor(sep_color);
+ mTimeBoxTextBox->setVisible(false);
+ }
+ else if (chat.mFromName.empty()
+ || mSourceType == CHAT_SOURCE_SYSTEM)
+ {
+ mFrom = LLTrans::getString("SECOND_LIFE");
+ if(!chat.mFromName.empty() && (mFrom != chat.mFromName))
+ {
+ mFrom += " (" + chat.mFromName + ")";
+ }
+ user_name->setValue(mFrom);
+ updateMinUserNameWidth();
+ }
+ else if (mSourceType == CHAT_SOURCE_AGENT
+ && !mAvatarID.isNull()
+ && chat.mChatStyle != CHAT_STYLE_HISTORY)
+ {
+ // ...from a normal user, lookup the name and fill in later.
+ // *NOTE: Do not do this for chat history logs, otherwise the viewer
+ // will flood the People API with lookup requests on startup
+
+ // Start with blank so sample data from XUI XML doesn't
+ // flash on the screen
+ user_name->setValue( LLSD() );
+ fetchAvatarName();
+ }
+ else if (chat.mChatStyle == CHAT_STYLE_HISTORY ||
+ mSourceType == CHAT_SOURCE_AGENT)
+ {
+ //if it's an avatar name with a username add formatting
+ S32 username_start = chat.mFromName.rfind(" (");
+ S32 username_end = chat.mFromName.rfind(')');
+
+ if (username_start != std::string::npos &&
+ username_end == (chat.mFromName.length() - 1))
+ {
+ mFrom = chat.mFromName.substr(0, username_start);
+ user_name->setValue(mFrom);
+
+ if (gSavedSettings.getBOOL("NameTagShowUsernames"))
+ {
+ std::string username = chat.mFromName.substr(username_start + 2);
+ username = username.substr(0, username.length() - 1);
+ LLStyle::Params style_params_name;
+ LLColor4 userNameColor = LLUIColorTable::instance().getColor("EmphasisColor");
+ style_params_name.color(userNameColor);
+ style_params_name.font.name("SansSerifSmall");
+ style_params_name.font.style("NORMAL");
+ style_params_name.readonly_color(userNameColor);
+ user_name->appendText(" - " + username, false, style_params_name);
+ }
+ }
+ else
+ {
+ mFrom = chat.mFromName;
+ user_name->setValue(mFrom);
+ updateMinUserNameWidth();
+ }
+ }
+ else
+ {
+ // ...from an object, just use name as given
+ mFrom = chat.mFromName;
+ user_name->setValue(mFrom);
+ updateMinUserNameWidth();
+ }
+
+
+ setTimeField(chat);
+
+ // Set up the icon.
+ LLAvatarIconCtrl* icon = getChild<LLAvatarIconCtrl>("avatar_icon");
+
+ if(mSourceType != CHAT_SOURCE_AGENT || mAvatarID.isNull())
+ icon->setDrawTooltip(false);
+
+ switch (mSourceType)
+ {
+ case CHAT_SOURCE_AGENT:
+ icon->setValue(chat.mFromID);
+ break;
+ case CHAT_SOURCE_OBJECT:
+ icon->setValue(LLSD("OBJECT_Icon"));
+ break;
+ case CHAT_SOURCE_SYSTEM:
+ case CHAT_SOURCE_REGION:
+ icon->setValue(LLSD("SL_Logo"));
+ break;
+ case CHAT_SOURCE_TELEPORT:
+ icon->setValue(LLSD("Command_Destinations_Icon"));
+ break;
+ case CHAT_SOURCE_UNKNOWN:
+ icon->setValue(LLSD("Unknown_Icon"));
+ }
+
+ // In case the message came from an object, save the object info
+ // to be able properly show its profile.
+ if ( chat.mSourceType == CHAT_SOURCE_OBJECT)
+ {
+ std::string slurl = args["slurl"].asString();
+ if (slurl.empty() && LLWorld::instanceExists())
+ {
+ LLViewerRegion *region = LLWorld::getInstance()->getRegionFromPosAgent(chat.mPosAgent);
+ if(region)
+ {
+ LLSLURL region_slurl(region->getName(), chat.mPosAgent);
+ slurl = region_slurl.getLocationString();
+ }
+ }
+
+ LLSD payload;
+ payload["object_id"] = chat.mFromID;
+ payload["name"] = chat.mFromName;
+ payload["owner_id"] = chat.mOwnerID;
+ payload["slurl"] = LLWeb::escapeURL(slurl);
+
+ mObjectData = payload;
+ }
+ }
+
+ /*virtual*/ void draw()
+ {
+ LLTextBox* user_name = mUserNameTextBox; //getChild<LLTextBox>("user_name");
+ LLTextBox* time_box = mTimeBoxTextBox; //getChild<LLTextBox>("time_box");
+
+ LLRect user_name_rect = user_name->getRect();
+ S32 user_name_width = user_name_rect.getWidth();
+ S32 time_box_width = time_box->getRect().getWidth();
+
+ if (mNeedsTimeBox && !time_box->getVisible() && user_name_width > mMinUserNameWidth)
+ {
+ user_name_rect.mRight -= time_box_width;
+ user_name->reshape(user_name_rect.getWidth(), user_name_rect.getHeight());
+ user_name->setRect(user_name_rect);
+
+ time_box->setVisible(true);
+ }
+
+ LLPanel::draw();
+ }
+
+ void updateMinUserNameWidth()
+ {
+ if (mUserNameFont)
+ {
+ LLTextBox* user_name = getChild<LLTextBox>("user_name");
+ const LLWString& text = user_name->getWText();
+ mMinUserNameWidth = mUserNameFont->getWidth(text.c_str()) + PADDING;
+ }
+ }
+
+protected:
+ static const S32 PADDING = 20;
+
+ void showContextMenu(S32 x,S32 y)
+ {
+ if(mSourceType == CHAT_SOURCE_SYSTEM)
+ showSystemContextMenu(x,y);
+ if(mAvatarID.notNull() && mSourceType == CHAT_SOURCE_AGENT)
+ showAvatarContextMenu(x,y);
+ if(mAvatarID.notNull() && mSourceType == CHAT_SOURCE_OBJECT)
+ showObjectContextMenu(x,y);
+ }
+
+ void showSystemContextMenu(S32 x,S32 y)
+ {
+ }
+
+ void showObjectContextMenu(S32 x,S32 y)
+ {
+ LLMenuGL* menu = (LLMenuGL*)mPopupMenuHandleObject.get();
+ if (!menu)
+ {
+ LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar;
+ LLUICtrl::EnableCallbackRegistry::ScopedRegistrar registrar_enable;
+ registrar.add("ObjectIcon.Action", boost::bind(&LLChatHistoryHeader::onObjectIconContextMenuItemClicked, this, _2));
+ registrar_enable.add("ObjectIcon.Visible", boost::bind(&LLChatHistoryHeader::onObjectIconContextMenuItemVisible, this, _2));
+
+ menu = LLUICtrlFactory::getInstance()->createFromFile<LLMenuGL>("menu_object_icon.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance());
+ if (menu)
+ {
+ mPopupMenuHandleObject = menu->getHandle();
+ menu->updateParent(LLMenuGL::sMenuContainer);
+ LLMenuGL::showPopup(this, menu, x, y);
+ }
+ else
+ {
+ LL_WARNS() << " Failed to create menu_object_icon.xml" << LL_ENDL;
+ }
+ }
+ else
+ {
+ LLMenuGL::showPopup(this, menu, x, y);
+ }
+ }
+
+ void showAvatarContextMenu(S32 x,S32 y)
+ {
+ LLMenuGL* menu = (LLMenuGL*)mPopupMenuHandleAvatar.get();
+ if (!menu)
+ {
+ LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar;
+ LLUICtrl::EnableCallbackRegistry::ScopedRegistrar registrar_enable;
+ registrar.add("AvatarIcon.Action", boost::bind(&LLChatHistoryHeader::onAvatarIconContextMenuItemClicked, this, _2));
+ registrar_enable.add("AvatarIcon.Check", boost::bind(&LLChatHistoryHeader::onAvatarIconContextMenuItemChecked, this, _2));
+ registrar_enable.add("AvatarIcon.Enable", boost::bind(&LLChatHistoryHeader::onAvatarIconContextMenuItemEnabled, this, _2));
+ registrar_enable.add("AvatarIcon.Visible", boost::bind(&LLChatHistoryHeader::onAvatarIconContextMenuItemVisible, this, _2));
+
+ menu = LLUICtrlFactory::getInstance()->createFromFile<LLMenuGL>("menu_avatar_icon.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance());
+ if (menu)
+ {
+ mPopupMenuHandleAvatar = menu->getHandle();
+ }
+ else
+ {
+ LL_WARNS() << " Failed to create menu_avatar_icon.xml" << LL_ENDL;
+ }
+ }
+
+ if(menu)
+ {
+ bool is_friend = LLAvatarActions::isFriend(mAvatarID);
+ bool is_group_session = gAgent.isInGroup(mSessionID);
+
+ menu->setItemEnabled("Add Friend", !is_friend);
+ menu->setItemEnabled("Remove Friend", is_friend);
+ menu->setItemVisible("Moderator Options Separator", is_group_session && isGroupModerator());
+ menu->setItemVisible("Moderator Options", is_group_session && isGroupModerator());
+ menu->setItemVisible("Group Ban Separator", is_group_session && canBanInGroup());
+ menu->setItemVisible("BanMember", is_group_session && canBanInGroup());
+
+ if(gAgentID == mAvatarID)
+ {
+ menu->setItemEnabled("Add Friend", false);
+ menu->setItemEnabled("Send IM", false);
+ menu->setItemEnabled("Remove Friend", false);
+ menu->setItemEnabled("Offer Teleport",false);
+ menu->setItemEnabled("Request Teleport",false);
+ menu->setItemEnabled("Voice Call", false);
+ menu->setItemEnabled("Chat History", false);
+ menu->setItemEnabled("Invite Group", false);
+ menu->setItemEnabled("Zoom In", false);
+ menu->setItemEnabled("Share", false);
+ menu->setItemEnabled("Pay", false);
+ menu->setItemEnabled("Block Unblock", false);
+ menu->setItemEnabled("Mute Text", false);
+ }
+ else
+ {
+ LLUUID currentSessionID = LLIMMgr::computeSessionID(IM_NOTHING_SPECIAL, mAvatarID);
+ if (mSessionID == currentSessionID)
+ {
+ menu->setItemVisible("Send IM", false);
+ }
+ menu->setItemEnabled("Offer Teleport", LLAvatarActions::canOfferTeleport(mAvatarID));
+ menu->setItemEnabled("Request Teleport", LLAvatarActions::canOfferTeleport(mAvatarID));
+ menu->setItemEnabled("Voice Call", LLAvatarActions::canCall());
+
+ // We should only show 'Zoom in' item in a nearby chat
+ bool should_show_zoom = !LLIMModel::getInstance()->findIMSession(currentSessionID);
+ menu->setItemVisible("Zoom In", should_show_zoom && gObjectList.findObject(mAvatarID));
+ menu->setItemEnabled("Block Unblock", LLAvatarActions::canBlock(mAvatarID));
+ menu->setItemEnabled("Mute Text", LLAvatarActions::canBlock(mAvatarID));
+ menu->setItemEnabled("Chat History", LLLogChat::isTranscriptExist(mAvatarID));
+ }
+
+ menu->setItemEnabled("Map", (LLAvatarTracker::instance().isBuddyOnline(mAvatarID) && is_agent_mappable(mAvatarID)) || gAgent.isGodlike() );
+ menu->buildDrawLabels();
+ menu->updateParent(LLMenuGL::sMenuContainer);
+ LLMenuGL::showPopup(this, menu, x, y);
+ }
+ }
+
+ void showInfoCtrl()
+ {
+ const bool isVisible = !mAvatarID.isNull() && !mFrom.empty() && CHAT_SOURCE_SYSTEM != mSourceType && CHAT_SOURCE_REGION != mSourceType;
+ if (isVisible)
+ {
+ const LLRect sticky_rect = mUserNameTextBox->getRect();
+ S32 icon_x = llmin(sticky_rect.mLeft + mUserNameTextBox->getTextBoundingRect().getWidth() + 7, sticky_rect.mRight - 3);
+ mInfoCtrl->setOrigin(icon_x, sticky_rect.getCenterY() - mInfoCtrl->getRect().getHeight() / 2 ) ;
+ }
+ mInfoCtrl->setVisible(isVisible);
+ }
+
+ void hideInfoCtrl()
+ {
+ mInfoCtrl->setVisible(false);
+ }
+
+private:
+ void setTimeField(const LLChat& chat)
+ {
+ LLTextBox* time_box = getChild<LLTextBox>("time_box");
+
+ LLRect rect_before = time_box->getRect();
+
+ time_box->setValue(chat.mTimeStr);
+
+ // set necessary textbox width to fit all text
+ time_box->reshapeToFitText();
+ LLRect rect_after = time_box->getRect();
+
+ // move rect to the left to correct position...
+ S32 delta_pos_x = rect_before.getWidth() - rect_after.getWidth();
+ S32 delta_pos_y = rect_before.getHeight() - rect_after.getHeight();
+ time_box->translate(delta_pos_x, delta_pos_y);
+
+ //... & change width of the name control
+ LLView* user_name = getChild<LLView>("user_name");
+ const LLRect& user_rect = user_name->getRect();
+ user_name->reshape(user_rect.getWidth() + delta_pos_x, user_rect.getHeight());
+ }
+
+ void fetchAvatarName()
+ {
+ if (mAvatarID.notNull())
+ {
+ if (mAvatarNameCacheConnection.connected())
+ {
+ mAvatarNameCacheConnection.disconnect();
+ }
+ mAvatarNameCacheConnection = LLAvatarNameCache::get(mAvatarID,
+ boost::bind(&LLChatHistoryHeader::onAvatarNameCache, this, _1, _2));
+ }
+ }
+
+ void onAvatarNameCache(const LLUUID& agent_id, const LLAvatarName& av_name)
+ {
+ mAvatarNameCacheConnection.disconnect();
+
+ mFrom = av_name.getDisplayName();
+
+ LLTextBox* user_name = getChild<LLTextBox>("user_name");
+ user_name->setValue( LLSD(av_name.getDisplayName() ) );
+ user_name->setToolTip( av_name.getUserName() );
+
+ if (gSavedSettings.getBOOL("NameTagShowUsernames") &&
+ av_name.useDisplayNames() &&
+ !av_name.isDisplayNameDefault())
+ {
+ LLStyle::Params style_params_name;
+ LLColor4 userNameColor = LLUIColorTable::instance().getColor("EmphasisColor");
+ style_params_name.color(userNameColor);
+ style_params_name.font.name("SansSerifSmall");
+ style_params_name.font.style("NORMAL");
+ style_params_name.readonly_color(userNameColor);
+ user_name->appendText(" - " + av_name.getUserName(), false, style_params_name);
+ }
+ setToolTip( av_name.getUserName() );
+ // name might have changed, update width
+ updateMinUserNameWidth();
+ }
+
+protected:
+ LLHandle<LLView> mPopupMenuHandleAvatar;
+ LLHandle<LLView> mPopupMenuHandleObject;
+
+ LLUICtrl* mInfoCtrl;
+
+ LLUUID mAvatarID;
+ LLSD mObjectData;
+ EChatSourceType mSourceType;
+ std::string mFrom;
+ LLUUID mSessionID;
+ std::string mText;
+ F64 mTime; // IM's frame time
+ time_t mCreationTime; // Views's time
+
+ S32 mMinUserNameWidth;
+ const LLFontGL* mUserNameFont;
+ LLTextBox* mUserNameTextBox;
+ LLTextBox* mTimeBoxTextBox;
+
+ bool mNeedsTimeBox;
+
+private:
+ boost::signals2::connection mAvatarNameCacheConnection;
+};
+
+LLChatHistory::LLChatHistory(const LLChatHistory::Params& p)
+: LLUICtrl(p),
+ mMessageHeaderFilename(p.message_header),
+ mMessageSeparatorFilename(p.message_separator),
+ mLeftTextPad(p.left_text_pad),
+ mRightTextPad(p.right_text_pad),
+ mLeftWidgetPad(p.left_widget_pad),
+ mRightWidgetPad(p.right_widget_pad),
+ mTopSeparatorPad(p.top_separator_pad),
+ mBottomSeparatorPad(p.bottom_separator_pad),
+ mTopHeaderPad(p.top_header_pad),
+ mBottomHeaderPad(p.bottom_header_pad),
+ mIsLastMessageFromLog(false),
+ mNotifyAboutUnreadMsg(p.notify_unread_msg)
+{
+ LLTextEditor::Params editor_params(p);
+ editor_params.rect = getLocalRect();
+ editor_params.follows.flags = FOLLOWS_ALL;
+ editor_params.enabled = false; // read only
+ editor_params.show_context_menu = "true";
+ editor_params.trusted_content = false;
+ editor_params.text_valign = LLFontGL::VAlign::VCENTER;
+ editor_params.use_color = true;
+ mEditor = LLUICtrlFactory::create<LLTextEditor>(editor_params, this);
+ mEditor->setIsFriendCallback(LLAvatarActions::isFriend);
+ mEditor->setIsObjectBlockedCallback(boost::bind(&LLMuteList::isMuted, LLMuteList::getInstance(), _1, _2, 0));
+
+}
+
+LLSD LLChatHistory::getValue() const
+{
+ return LLSD(mEditor->getText());
+}
+
+LLChatHistory::~LLChatHistory()
+{
+ this->clear();
+}
+
+void LLChatHistory::initFromParams(const LLChatHistory::Params& p)
+{
+ static LLUICachedControl<S32> scrollbar_size ("UIScrollbarSize", 0);
+
+ LLRect stack_rect = getLocalRect();
+ stack_rect.mRight -= scrollbar_size;
+ LLLayoutStack::Params layout_p;
+ layout_p.rect = stack_rect;
+ layout_p.follows.flags = FOLLOWS_ALL;
+ layout_p.orientation = LLLayoutStack::VERTICAL;
+ layout_p.mouse_opaque = false;
+
+ LLLayoutStack* stackp = LLUICtrlFactory::create<LLLayoutStack>(layout_p, this);
+
+ const S32 NEW_TEXT_NOTICE_HEIGHT = 20;
+
+ LLLayoutPanel::Params panel_p;
+ panel_p.name = "spacer";
+ panel_p.background_visible = false;
+ panel_p.has_border = false;
+ panel_p.mouse_opaque = false;
+ panel_p.min_dim = 30;
+ panel_p.auto_resize = true;
+ panel_p.user_resize = false;
+
+ stackp->addPanel(LLUICtrlFactory::create<LLLayoutPanel>(panel_p), LLLayoutStack::ANIMATE);
+
+ panel_p.name = "new_text_notice_holder";
+ LLRect new_text_notice_rect = getLocalRect();
+ new_text_notice_rect.mTop = new_text_notice_rect.mBottom + NEW_TEXT_NOTICE_HEIGHT;
+ panel_p.rect = new_text_notice_rect;
+ panel_p.background_opaque = true;
+ panel_p.background_visible = true;
+ panel_p.visible = false;
+ panel_p.min_dim = 0;
+ panel_p.auto_resize = false;
+ panel_p.user_resize = false;
+ mMoreChatPanel = LLUICtrlFactory::create<LLLayoutPanel>(panel_p);
+
+ LLTextBox::Params text_p(p.more_chat_text);
+ text_p.rect = mMoreChatPanel->getLocalRect();
+ text_p.follows.flags = FOLLOWS_ALL;
+ text_p.name = "more_chat_text";
+ mMoreChatText = LLUICtrlFactory::create<LLTextBox>(text_p, mMoreChatPanel);
+ mMoreChatText->setClickedCallback(boost::bind(&LLChatHistory::onClickMoreText, this));
+
+ stackp->addPanel(mMoreChatPanel, LLLayoutStack::ANIMATE);
+}
+
+
+/*void LLChatHistory::updateTextRect()
+{
+ static LLUICachedControl<S32> texteditor_border ("UITextEditorBorder", 0);
+
+ LLRect old_text_rect = mVisibleTextRect;
+ mVisibleTextRect = mScroller->getContentWindowRect();
+ mVisibleTextRect.stretch(-texteditor_border);
+ mVisibleTextRect.mLeft += mLeftTextPad;
+ mVisibleTextRect.mRight -= mRightTextPad;
+ if (mVisibleTextRect != old_text_rect)
+ {
+ needsReflow();
+ }
+}*/
+
+LLView* LLChatHistory::getSeparator()
+{
+ LLPanel* separator = LLUICtrlFactory::getInstance()->createFromFile<LLPanel>(mMessageSeparatorFilename, NULL, LLPanel::child_registry_t::instance());
+ return separator;
+}
+
+LLView* LLChatHistory::getHeader(const LLChat& chat,const LLStyle::Params& style_params, const LLSD& args)
+{
+ LLChatHistoryHeader* header = LLChatHistoryHeader::createInstance(mMessageHeaderFilename);
+ if (header)
+ header->setup(chat, style_params, args);
+ return header;
+}
+
+void LLChatHistory::onClickMoreText()
+{
+ mEditor->endOfDoc();
+}
+
+void LLChatHistory::clear()
+{
+ mLastFromName.clear();
+ mEditor->clear();
+ mLastFromID = LLUUID::null;
+}
+
+static LLTrace::BlockTimerStatHandle FTM_APPEND_MESSAGE("Append Chat Message");
+
+void LLChatHistory::appendMessage(const LLChat& chat, const LLSD &args, const LLStyle::Params& input_append_params)
+{
+ LL_RECORD_BLOCK_TIME(FTM_APPEND_MESSAGE);
+ bool use_plain_text_chat_history = args["use_plain_text_chat_history"].asBoolean();
+ bool square_brackets = false; // square brackets necessary for a system messages
+
+ llassert(mEditor);
+ if (!mEditor)
+ return;
+
+ bool from_me = chat.mFromID == gAgent.getID();
+ mEditor->setPlainText(use_plain_text_chat_history);
+
+ if (mNotifyAboutUnreadMsg && !mEditor->scrolledToEnd() && !from_me && !chat.mFromName.empty())
+ {
+ mUnreadChatSources.insert(chat.mFromName);
+ mMoreChatPanel->setVisible(true);
+ std::string chatters;
+ for (const std::string& source : mUnreadChatSources)
+ {
+ chatters += chatters.size() ? ", " + source : source;
+ }
+ LLStringUtil::format_map_t args;
+ args["SOURCES"] = chatters;
+
+ std::string xml_desc = mUnreadChatSources.size() == 1 ?
+ "unread_chat_single" : "unread_chat_multiple";
+ mMoreChatText->setValue(LLTrans::getString(xml_desc, args));
+ S32 height = mMoreChatText->getTextPixelHeight() + 5;
+ mMoreChatPanel->reshape(mMoreChatPanel->getRect().getWidth(), height);
+ }
+
+ LLColor4 txt_color = LLUIColorTable::instance().getColor("White");
+ LLColor4 name_color(txt_color);
+
+ LLViewerChat::getChatColor(chat,txt_color);
+ LLFontGL* fontp = LLViewerChat::getChatFont();
+ std::string font_name = LLFontGL::nameFromFont(fontp);
+ std::string font_size = LLFontGL::sizeFromFont(fontp);
+
+ LLStyle::Params body_message_params;
+ body_message_params.color(txt_color);
+ body_message_params.readonly_color(txt_color);
+ body_message_params.font.name(font_name);
+ body_message_params.font.size(font_size);
+ body_message_params.font.style(input_append_params.font.style);
+
+ LLStyle::Params name_params(body_message_params);
+ name_params.color(name_color);
+ name_params.readonly_color(name_color);
+
+ std::string prefix = chat.mText.substr(0, 4);
+
+ //IRC styled /me messages.
+ bool irc_me = prefix == "/me " || prefix == "/me'";
+
+ // Delimiter after a name in header copy/past and in plain text mode
+ std::string delimiter = ": ";
+ std::string shout = LLTrans::getString("shout");
+ std::string whisper = LLTrans::getString("whisper");
+ if (chat.mChatType == CHAT_TYPE_SHOUT ||
+ chat.mChatType == CHAT_TYPE_WHISPER ||
+ chat.mText.compare(0, shout.length(), shout) == 0 ||
+ chat.mText.compare(0, whisper.length(), whisper) == 0)
+ {
+ delimiter = " ";
+ }
+
+ // Don't add any delimiter after name in irc styled messages
+ if (irc_me || chat.mChatStyle == CHAT_STYLE_IRC)
+ {
+ delimiter = LLStringUtil::null;
+ body_message_params.font.style = "ITALIC";
+ }
+
+ if (chat.mChatType == CHAT_TYPE_WHISPER)
+ {
+ body_message_params.font.style = "ITALIC";
+ }
+ else if (chat.mChatType == CHAT_TYPE_SHOUT)
+ {
+ body_message_params.font.style = "BOLD";
+ }
+
+ bool message_from_log = chat.mChatStyle == CHAT_STYLE_HISTORY;
+ bool teleport_separator = chat.mSourceType == CHAT_SOURCE_TELEPORT;
+ // We graying out chat history by graying out messages that contains full date in a time string
+ if (message_from_log)
+ {
+ txt_color = LLColor4::grey;
+ body_message_params.color(txt_color);
+ body_message_params.readonly_color(txt_color);
+ name_params.color(txt_color);
+ name_params.readonly_color(txt_color);
+ }
+
+ bool prependNewLineState = mEditor->getText().size() != 0;
+
+ // compact mode: show a timestamp and name
+ if (use_plain_text_chat_history)
+ {
+ square_brackets = chat.mSourceType == CHAT_SOURCE_SYSTEM;
+
+ LLStyle::Params timestamp_style(body_message_params);
+
+ // out of the timestamp
+ if (args["show_time"].asBoolean() && !teleport_separator)
+ {
+ if (!message_from_log)
+ {
+ LLColor4 timestamp_color = LLUIColorTable::instance().getColor("ChatTimestampColor");
+ timestamp_style.color(timestamp_color);
+ timestamp_style.readonly_color(timestamp_color);
+ }
+ mEditor->appendText("[" + chat.mTimeStr + "] ", prependNewLineState, timestamp_style);
+ prependNewLineState = false;
+ }
+
+ // out the opening square bracket (if need)
+ if (square_brackets)
+ {
+ mEditor->appendText("[", prependNewLineState, body_message_params);
+ prependNewLineState = false;
+ }
+
+ // names showing
+ if (args["show_names_for_p2p_conv"].asBoolean() && utf8str_trim(chat.mFromName).size())
+ {
+ // Don't hotlink any messages from the system (e.g. "Second Life:"), so just add those in plain text.
+ if (chat.mSourceType == CHAT_SOURCE_OBJECT && chat.mFromID.notNull())
+ {
+ // for object IMs, create a secondlife:///app/objectim SLapp
+ std::string url = LLViewerChat::getSenderSLURL(chat, args);
+
+ // set the link for the object name to be the objectim SLapp
+ // (don't let object names with hyperlinks override our objectim Url)
+ LLStyle::Params link_params(body_message_params);
+ LLColor4 link_color = LLUIColorTable::instance().getColor("HTMLLinkColor");
+ link_params.color = link_color;
+ link_params.readonly_color = link_color;
+ link_params.is_link = true;
+ link_params.link_href = url;
+
+ mEditor->appendText(chat.mFromName + delimiter, prependNewLineState, link_params);
+ prependNewLineState = false;
+ }
+ else if ( chat.mFromName != SYSTEM_FROM && chat.mFromID.notNull() && !message_from_log && chat.mSourceType != CHAT_SOURCE_REGION)
+ {
+ LLStyle::Params link_params(body_message_params);
+ link_params.overwriteFrom(LLStyleMap::instance().lookupAgent(chat.mFromID));
+
+ // Add link to avatar's inspector and delimiter to message.
+ mEditor->appendText(std::string(link_params.link_href) + delimiter,
+ prependNewLineState, link_params);
+ prependNewLineState = false;
+ }
+ else if (teleport_separator)
+ {
+ std::string tp_text = LLTrans::getString("teleport_preamble_compact_chat");
+ mEditor->appendText(tp_text + " <nolink>" + chat.mFromName + "</nolink>",
+ prependNewLineState, body_message_params);
+ prependNewLineState = false;
+ }
+ else
+ {
+ mEditor->appendText("<nolink>" + chat.mFromName + "</nolink>" + delimiter,
+ prependNewLineState, body_message_params);
+ prependNewLineState = false;
+ }
+ }
+ }
+ else // showing timestamp and name in the expanded mode
+ {
+ prependNewLineState = false;
+ LLView* view = NULL;
+ LLInlineViewSegment::Params p;
+ p.force_newline = true;
+ p.left_pad = mLeftWidgetPad;
+ p.right_pad = mRightWidgetPad;
+
+ LLDate new_message_time = LLDate::now();
+ if (!teleport_separator
+ && mLastFromName == chat.mFromName
+ && mLastFromID == chat.mFromID
+ && mLastMessageTime.notNull()
+ && (new_message_time.secondsSinceEpoch() - mLastMessageTime.secondsSinceEpoch()) < 60.0
+ && mIsLastMessageFromLog == message_from_log) //distinguish between current and previous chat session's histories
+ {
+ view = getSeparator();
+ if (!view)
+ {
+ // Might be wiser to make this LL_ERRS, getSeparator() should work in case of correct instalation.
+ LL_WARNS() << "Failed to create separator from " << mMessageSeparatorFilename << ": can't append to history" << LL_ENDL;
+ return;
+ }
+
+ p.top_pad = mTopSeparatorPad;
+ p.bottom_pad = mBottomSeparatorPad;
+ }
+ else
+ {
+ view = getHeader(chat, name_params, args);
+ if (!view)
+ {
+ LL_WARNS() << "Failed to create header from " << mMessageHeaderFilename << ": can't append to history" << LL_ENDL;
+ return;
+ }
+
+ p.top_pad = mEditor->getLength() ? mTopHeaderPad : 0;
+ p.bottom_pad = teleport_separator ? mBottomSeparatorPad : mBottomHeaderPad;
+ }
+ p.view = view;
+
+ //Prepare the rect for the view
+ LLRect target_rect = mEditor->getDocumentView()->getRect();
+ // squeeze down the widget by subtracting padding off left and right
+ target_rect.mLeft += mLeftWidgetPad + mEditor->getHPad();
+ target_rect.mRight -= mRightWidgetPad;
+ view->reshape(target_rect.getWidth(), view->getRect().getHeight());
+ view->setOrigin(target_rect.mLeft, view->getRect().mBottom);
+
+ std::string widget_associated_text = "\n[" + chat.mTimeStr + "] ";
+ if (utf8str_trim(chat.mFromName).size() != 0 && chat.mFromName != SYSTEM_FROM)
+ widget_associated_text += chat.mFromName + delimiter;
+
+ mEditor->appendWidget(p, widget_associated_text, false);
+ mLastFromName = chat.mFromName;
+ mLastFromID = chat.mFromID;
+ mLastMessageTime = new_message_time;
+ mIsLastMessageFromLog = message_from_log;
+ }
+
+ // body of the message processing
+
+ // notify processing
+ if (chat.mNotifId.notNull())
+ {
+ LLNotificationPtr notification = LLNotificationsUtil::find(chat.mNotifId);
+ if (notification != NULL)
+ {
+ bool create_toast = true;
+ if (notification->getName() == "OfferFriendship")
+ {
+ // We don't want multiple friendship offers to appear, this code checks if there are previous offers
+ // by iterating though all panels.
+ // Note: it might be better to simply add a "pending offer" flag somewhere
+ for (auto& panel : LLToastNotifyPanel::instance_snapshot())
+ {
+ LLIMToastNotifyPanel * imtoastp = dynamic_cast<LLIMToastNotifyPanel *>(&panel);
+ const std::string& notification_name = panel.getNotificationName();
+ if (notification_name == "OfferFriendship"
+ && panel.isControlPanelEnabled()
+ && imtoastp)
+ {
+ create_toast = false;
+ break;
+ }
+ }
+ }
+
+ if (create_toast)
+ {
+ LLIMToastNotifyPanel* notify_box = new LLIMToastNotifyPanel(
+ notification, chat.mSessionID, LLRect::null, !use_plain_text_chat_history, mEditor);
+
+ //Prepare the rect for the view
+ LLRect target_rect = mEditor->getDocumentView()->getRect();
+ // squeeze down the widget by subtracting padding off left and right
+ target_rect.mLeft += mLeftWidgetPad + mEditor->getHPad();
+ target_rect.mRight -= mRightWidgetPad;
+ notify_box->reshape(target_rect.getWidth(), notify_box->getRect().getHeight());
+ notify_box->setOrigin(target_rect.mLeft, notify_box->getRect().mBottom);
+
+ LLInlineViewSegment::Params params;
+ params.view = notify_box;
+ params.left_pad = mLeftWidgetPad;
+ params.right_pad = mRightWidgetPad;
+ mEditor->appendWidget(params, "\n", false);
+ }
+ }
+ }
+ // usual messages showing
+ else if (!teleport_separator)
+ {
+ std::string message = irc_me ? chat.mText.substr(3) : chat.mText;
+
+ //MESSAGE TEXT PROCESSING
+ //*HACK getting rid of redundant sender names in system notifications sent using sender name (see EXT-5010)
+ if (use_plain_text_chat_history && !from_me && chat.mFromID.notNull())
+ {
+ std::string slurl_about = SLURL_APP_AGENT + chat.mFromID.asString() + SLURL_ABOUT;
+ if (message.length() > slurl_about.length() &&
+ message.compare(0, slurl_about.length(), slurl_about) == 0)
+ {
+ message = message.substr(slurl_about.length(), message.length()-1);
+ }
+ }
+
+ if (irc_me && !use_plain_text_chat_history)
+ {
+ std::string from_name = chat.mFromName;
+ LLAvatarName av_name;
+ if (!chat.mFromID.isNull() &&
+ LLAvatarNameCache::get(chat.mFromID, &av_name) &&
+ !av_name.isDisplayNameDefault())
+ {
+ from_name = av_name.getCompleteName();
+ }
+ message = from_name + message;
+ }
+
+ if (square_brackets)
+ {
+ message += "]";
+ }
+
+ mEditor->appendText(message, prependNewLineState, body_message_params);
+ prependNewLineState = false;
+ }
+
+ mEditor->blockUndo();
+
+ // automatically scroll to end when receiving chat from myself
+ if (from_me)
+ {
+ mEditor->setCursorAndScrollToEnd();
+ }
+}
+
+void LLChatHistory::draw()
+{
+ if (mEditor->scrolledToEnd())
+ {
+ mUnreadChatSources.clear();
+ mMoreChatPanel->setVisible(false);
+ }
+
+ LLUICtrl::draw();
+}
|