/** * @file llimview.cpp * @brief Container for Instant Messaging * * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. * $License$ */ #include "llviewerprecompiledheaders.h" #include "llimview.h" #include "llfontgl.h" #include "llrect.h" #include "llerror.h" #include "llbutton.h" #include "llstring.h" #include "linked_lists.h" #include "llvieweruictrlfactory.h" #include "llagent.h" #include "llcallingcard.h" #include "llviewerwindow.h" #include "llresmgr.h" #include "llfloaternewim.h" #include "llimpanel.h" #include "llresizebar.h" #include "lltabcontainer.h" #include "viewer.h" #include "llfloater.h" #include "llresizehandle.h" #include "llkeyboard.h" #include "llui.h" #include "llviewermenu.h" #include "llcallingcard.h" #include "lltoolbar.h" const EInstantMessage EVERYONE_DIALOG = IM_NOTHING_SPECIAL; const EInstantMessage GROUP_DIALOG = IM_SESSION_GROUP_START; const EInstantMessage DEFAULT_DIALOG = IM_NOTHING_SPECIAL; // // Globals // LLIMView* gIMView = NULL; // // Statics // static LLString sOnlyUserMessage; static LLString sOfflineMessage; // // Helper Functions // // returns true if a should appear before b BOOL group_dictionary_sort( LLGroupData* a, LLGroupData* b ) { return (LLString::compareDict( a->mName, b->mName ) < 0); } // the other_participant_id is either an agent_id, a group_id, or an inventory // folder item_id (collection of calling cards) LLUUID compute_session_id(EInstantMessage dialog, const LLUUID& other_participant_id) { LLUUID session_id; if (IM_SESSION_GROUP_START == dialog) { // slam group session_id to the group_id (other_participant_id) session_id = other_participant_id; } else { LLUUID agent_id = gAgent.getID(); if (other_participant_id == agent_id) { // if we try to send an IM to ourselves then the XOR would be null // so we just make the session_id the same as the agent_id session_id = agent_id; } else { // peer-to-peer or peer-to-asset session_id is the XOR session_id = other_participant_id ^ agent_id; } } return session_id; } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // LLFloaterIM //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ LLFloaterIM::LLFloaterIM() { } BOOL LLFloaterIM::postBuild() { requires("only_user_message", WIDGET_TYPE_TEXT_BOX); requires("offline_message", WIDGET_TYPE_TEXT_BOX); if (checkRequirements()) { sOnlyUserMessage = childGetText("only_user_message"); sOfflineMessage = childGetText("offline_message"); return TRUE; } return FALSE; } //// virtual //BOOL LLFloaterIM::handleKeyHere(KEY key, MASK mask, BOOL called_from_parent) //{ // BOOL handled = FALSE; // if (getEnabled() // && mask == (MASK_CONTROL|MASK_SHIFT)) // { // if (key == 'W') // { // LLFloater* floater = getActiveFloater(); // if (floater) // { // if (mTabContainer->getTabCount() == 1) // { // // trying to close last tab, close // // entire window. // close(); // handled = TRUE; // } // } // } // } // return handled || LLMultiFloater::handleKeyHere(key, mask, called_from_parent); //} void LLFloaterIM::onClose(bool app_quitting) { if (!app_quitting) { gSavedSettings.setBOOL("ShowIM", FALSE); } setVisible(FALSE); } //virtual void LLFloaterIM::addFloater(LLFloater* floaterp, BOOL select_added_floater, LLTabContainer::eInsertionPoint insertion_point) { // this code is needed to fix the bug where new IMs received will resize the IM floater. // SL-29075, SL-24556, and others LLRect parent_rect = getRect(); S32 dheight = LLFLOATER_HEADER_SIZE + TABCNTR_HEADER_HEIGHT; LLRect rect(0, parent_rect.getHeight()-dheight, parent_rect.getWidth(), 0); floaterp->reshape(rect.getWidth(), rect.getHeight(), TRUE); LLMultiFloater::addFloater(floaterp, select_added_floater, insertion_point); } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Class LLIMViewFriendObserver // // Bridge to suport knowing when the inventory has changed. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ class LLIMViewFriendObserver : public LLFriendObserver { public: LLIMViewFriendObserver(LLIMView* tv) : mTV(tv) {} virtual ~LLIMViewFriendObserver() {} virtual void changed(U32 mask) { if(mask & (LLFriendObserver::ADD | LLFriendObserver::REMOVE | LLFriendObserver::ONLINE)) { mTV->refresh(); } } protected: LLIMView* mTV; }; // // Public Static Member Functions // // This is a helper function to determine what kind of im session // should be used for the given agent. // static EInstantMessage LLIMView::defaultIMTypeForAgent(const LLUUID& agent_id) { EInstantMessage type = IM_SESSION_CARDLESS_START; if(is_agent_friend(agent_id)) { if(LLAvatarTracker::instance().isBuddyOnline(agent_id)) { type = IM_SESSION_ADD; } else { type = IM_SESSION_OFFLINE_ADD; } } return type; } // static //void LLIMView::onPinButton(void*) //{ // BOOL state = gSavedSettings.getBOOL( "PinTalkViewOpen" ); // gSavedSettings.setBOOL( "PinTalkViewOpen", !state ); //} // static void LLIMView::toggle(void*) { static BOOL return_to_mouselook = FALSE; // Hide the button and show the floater or vice versa. llassert( gIMView ); BOOL old_state = gIMView->getFloaterOpen(); // If we're in mouselook and we triggered the Talk View, we want to talk. if( gAgent.cameraMouselook() && old_state ) { return_to_mouselook = TRUE; gAgent.changeCameraToDefault(); return; } BOOL new_state = !old_state; if (new_state) { // ...making visible if ( gAgent.cameraMouselook() ) { return_to_mouselook = TRUE; gAgent.changeCameraToDefault(); } } else { // ...hiding if ( gAgent.cameraThirdPerson() && return_to_mouselook ) { gAgent.changeCameraToMouselook(); } return_to_mouselook = FALSE; } gIMView->setFloaterOpen( new_state ); } // // Member Functions // LLIMView::LLIMView(const std::string& name, const LLRect& rect) : LLView(name, rect, FALSE), mFriendObserver(NULL), mIMReceived(FALSE) { gIMView = this; mFriendObserver = new LLIMViewFriendObserver(this); LLAvatarTracker::instance().addObserver(mFriendObserver); mTalkFloater = new LLFloaterIM(); gUICtrlFactory->buildFloater(mTalkFloater, "floater_im.xml"); // New IM Panel mNewIMFloater = new LLFloaterNewIM(); mTalkFloater->addFloater(mNewIMFloater, TRUE); // Tabs sometimes overlap resize handle mTalkFloater->moveResizeHandleToFront(); } LLIMView::~LLIMView() { LLAvatarTracker::instance().removeObserver(mFriendObserver); delete mFriendObserver; // Children all cleaned up by default view destructor. } EWidgetType LLIMView::getWidgetType() const { return WIDGET_TYPE_TALK_VIEW; } LLString LLIMView::getWidgetTag() const { return LL_TALK_VIEW_TAG; } // Add a message to a session. void LLIMView::addMessage( const LLUUID& session_id, const LLUUID& other_participant_id, const char* from, const char* msg, const char* session_name, EInstantMessage dialog, U32 parent_estate_id, const LLUUID& region_id, const LLVector3& position) { LLFloaterIMPanel* floater; LLUUID new_session_id = session_id; if (new_session_id.isNull()) { new_session_id = compute_session_id(dialog, other_participant_id); } floater = findFloaterBySession(new_session_id); if (!floater) { floater = findFloaterBySession(other_participant_id); if (floater) { llinfos << "found the IM session " << session_id << " by participant " << other_participant_id << llendl; } } if(floater) { floater->addHistoryLine(msg); } else { const char* name = from; if(session_name && (strlen(session_name)>1)) { name = session_name; } floater = createFloater(new_session_id, other_participant_id, name, dialog, FALSE); // When we get a new IM, and if you are a god, display a bit // of information about the source. This is to help liaisons // when answering questions. if(gAgent.isGodlike()) { // XUI:translate std::ostringstream bonus_info; bonus_info << "*** parent estate: " << parent_estate_id << ((parent_estate_id == 1) ? ", mainland" : "") << ((parent_estate_id == 5) ? ", teen" : ""); // once we have web-services (or something) which returns // information about a region id, we can print this out // and even have it link to map-teleport or something. //<< "*** region_id: " << region_id << std::endl //<< "*** position: " << position << std::endl; floater->addHistoryLine(bonus_info.str()); } floater->addHistoryLine(msg); make_ui_sound("UISndNewIncomingIMSession"); } if( !mTalkFloater->getVisible() && !floater->getVisible()) { //if the IM window is not open and the floater is not visible (i.e. not torn off) LLFloater* previouslyActiveFloater = mTalkFloater->getActiveFloater(); // select the newly added floater (or the floater with the new line added to it). // it should be there. mTalkFloater->selectFloater(floater); //there was a previously unseen IM, make that old tab flashing //it is assumed that the most recently unseen IM tab is the one current selected/active if ( previouslyActiveFloater && getIMReceived() ) { mTalkFloater->setFloaterFlashing(previouslyActiveFloater, TRUE); } //notify of a new IM notifyNewIM(); } } void LLIMView::notifyNewIM() { if(!gIMView->getFloaterOpen()) { mIMReceived = TRUE; } } BOOL LLIMView::getIMReceived() const { return mIMReceived; } // This method returns TRUE if the local viewer has a session // currently open keyed to the uuid. BOOL LLIMView::isIMSessionOpen(const LLUUID& uuid) { LLFloaterIMPanel* floater = findFloaterBySession(uuid); if(floater) return TRUE; return FALSE; } // This adds a session to the talk view. The name is the local name of // the session, dialog specifies the type of session. If the session // exists, it is brought forward. Specifying id = NULL results in an // im session to everyone. Returns the uuid of the session. LLUUID LLIMView::addSession(const std::string& name, EInstantMessage dialog, const LLUUID& other_participant_id) { LLUUID session_id = compute_session_id(dialog, other_participant_id); LLFloaterIMPanel* floater = findFloaterBySession(session_id); if(!floater) { floater = createFloater(session_id, other_participant_id, name, dialog, TRUE); LLDynamicArray ids; ids.put(other_participant_id); noteOfflineUsers(floater, ids); floater->addParticipants(ids); mTalkFloater->showFloater(floater); } else { floater->open(); } //mTabContainer->selectTabPanel(panel); floater->setInputFocus(TRUE); return floater->getSessionID(); } // Adds a session using the given session_id. If the session already exists // the dialog type is assumed correct. Returns the uuid of the session. LLUUID LLIMView::addSession(const std::string& name, EInstantMessage dialog, const LLUUID& session_id, const LLDynamicArray& ids) { if (0 == ids.getLength()) { return LLUUID::null; } LLUUID other_participant_id = ids[0]; LLUUID new_session_id = session_id; if (new_session_id.isNull()) { new_session_id = compute_session_id(dialog, other_participant_id); } LLFloaterIMPanel* floater = findFloaterBySession(new_session_id); if(!floater) { // On creation, use the first element of ids as the "other_participant_id" floater = createFloater(new_session_id, other_participant_id, name, dialog, TRUE); noteOfflineUsers(floater, ids); } floater->addParticipants(ids); mTalkFloater->showFloater(floater); //mTabContainer->selectTabPanel(panel); floater->setInputFocus(TRUE); return floater->getSessionID(); } // This removes the panel referenced by the uuid, and then restores // internal consistency. The internal pointer is not deleted. void LLIMView::removeSession(const LLUUID& session_id) { LLFloaterIMPanel* floater = findFloaterBySession(session_id); if(floater) { mFloaters.erase(floater->getHandle()); mTalkFloater->removeFloater(floater); //mTabContainer->removeTabPanel(floater); } } void LLIMView::refresh() { S32 old_scroll_pos = mNewIMFloater->getScrollPos(); mNewIMFloater->clearAllTargets(); // build a list of groups. LLLinkedList group_list( group_dictionary_sort ); LLGroupData* group; S32 count = gAgent.mGroups.count(); S32 i; // read/sort groups on the first pass. for(i = 0; i < count; ++i) { group = &(gAgent.mGroups.get(i)); group_list.addDataSorted( group ); } // add groups to the floater on the second pass. for(group = group_list.getFirstData(); group; group = group_list.getNextData()) { mNewIMFloater->addTarget(group->mID, group->mName, (void*)(&GROUP_DIALOG), TRUE, FALSE); } // build a set of buddies in the current buddy list. LLCollectAllBuddies collector; LLAvatarTracker::instance().applyFunctor(collector); LLCollectAllBuddies::buddy_map_t::iterator it; LLCollectAllBuddies::buddy_map_t::iterator end; it = collector.mOnline.begin(); end = collector.mOnline.end(); for( ; it != end; ++it) { mNewIMFloater->addAgent((*it).second, (void*)(&DEFAULT_DIALOG), TRUE); } it = collector.mOffline.begin(); end = collector.mOffline.end(); for( ; it != end; ++it) { mNewIMFloater->addAgent((*it).second, (void*)(&DEFAULT_DIALOG), FALSE); } if(gAgent.isGodlike()) { // XUI:translate mNewIMFloater->addTarget(LLUUID::null, "All Residents, All Grids", (void*)(&EVERYONE_DIALOG), TRUE, FALSE); } mNewIMFloater->setScrollPos( old_scroll_pos ); } // JC - This used to set console visibility. It doesn't any more. void LLIMView::setFloaterOpen(BOOL set_open) { gSavedSettings.setBOOL("ShowIM", set_open); //RN "visible" and "open" are considered synonomous for now if (set_open) { mTalkFloater->open(); } else { mTalkFloater->close(); } if( set_open ) { // notifyNewIM(); // We're showing the IM, so mark view as non-pending mIMReceived = FALSE; } } BOOL LLIMView::getFloaterOpen() { return mTalkFloater->getVisible(); } void LLIMView::pruneSessions() { if(mNewIMFloater) { BOOL removed = TRUE; LLFloaterIMPanel* floater = NULL; while(removed) { removed = FALSE; std::set::iterator handle_it; for(handle_it = mFloaters.begin(); handle_it != mFloaters.end(); ++handle_it) { floater = (LLFloaterIMPanel*)LLFloater::getFloaterByHandle(*handle_it); if(floater && !mNewIMFloater->isUUIDAvailable(floater->getOtherParticipantID())) { // remove this floater removed = TRUE; mFloaters.erase(handle_it++); floater->close(); break; } } } } } void LLIMView::disconnectAllSessions() { if(mNewIMFloater) { mNewIMFloater->setEnabled(FALSE); } LLFloaterIMPanel* floater = NULL; std::set::iterator handle_it; for(handle_it = mFloaters.begin(); handle_it != mFloaters.end(); ++handle_it) { floater = (LLFloaterIMPanel*)LLFloater::getFloaterByHandle(*handle_it); if (floater) { floater->setEnabled(FALSE); } } } // This method returns the im panel corresponding to the uuid // provided. The uuid can either be a session id or an agent // id. Returns NULL if there is no matching panel. LLFloaterIMPanel* LLIMView::findFloaterBySession(const LLUUID& session_id) { LLFloaterIMPanel* rv = NULL; std::set::iterator handle_it; for(handle_it = mFloaters.begin(); handle_it != mFloaters.end(); ++handle_it) { rv = (LLFloaterIMPanel*)LLFloater::getFloaterByHandle(*handle_it); if(rv && session_id == rv->getSessionID()) { break; } rv = NULL; } return rv; } BOOL LLIMView::hasSession(const LLUUID& session_id) { return (findFloaterBySession(session_id) != NULL); } // create a floater and update internal representation for // consistency. Returns the pointer, caller (the class instance since // it is a private method) is not responsible for deleting the // pointer. Add the floater to this but do not select it. LLFloaterIMPanel* LLIMView::createFloater(const LLUUID& session_id, const LLUUID& other_participant_id, const std::string& session_label, EInstantMessage dialog, BOOL user_initiated) { if (session_id.isNull()) { llwarns << "Creating LLFloaterIMPanel with null session ID" << llendl; } llinfos << "LLIMView::createFloater: from " << other_participant_id << " in session " << session_id << llendl; LLFloaterIMPanel* floater = new LLFloaterIMPanel(session_label, LLRect(), session_label, session_id, other_participant_id, dialog); LLTabContainerCommon::eInsertionPoint i_pt = user_initiated ? LLTabContainerCommon::RIGHT_OF_CURRENT : LLTabContainerCommon::END; mTalkFloater->addFloater(floater, FALSE, i_pt); mFloaters.insert(floater->getHandle()); return floater; } void LLIMView::noteOfflineUsers(LLFloaterIMPanel* floater, const LLDynamicArray& ids) { S32 count = ids.count(); if(count == 0) { floater->addHistoryLine(sOnlyUserMessage); } else { const LLRelationship* info = NULL; LLAvatarTracker& at = LLAvatarTracker::instance(); for(S32 i = 0; i < count; ++i) { info = at.getBuddyInfo(ids.get(i)); char first[DB_FIRST_NAME_BUF_SIZE]; char last[DB_LAST_NAME_BUF_SIZE]; if(info && !info->isOnline() && gCacheName->getName(ids.get(i), first, last)) { LLUIString offline = sOfflineMessage; offline.setArg("[FIRST]", first); offline.setArg("[LAST]", last); floater->addHistoryLine(offline); } } } } void LLIMView::processIMTypingStart(const LLIMInfo* im_info) { processIMTypingCore(im_info, TRUE); } void LLIMView::processIMTypingStop(const LLIMInfo* im_info) { processIMTypingCore(im_info, FALSE); } void LLIMView::processIMTypingCore(const LLIMInfo* im_info, BOOL typing) { LLUUID session_id = compute_session_id(im_info->mIMType, im_info->mFromID); LLFloaterIMPanel* floater = findFloaterBySession(session_id); if (floater) { floater->processIMTyping(im_info, typing); } }