From f22f829b8642d59cb5953eb33f5297a1da90fadd Mon Sep 17 00:00:00 2001 From: Eugene Mutavchi Date: Mon, 26 Oct 2009 17:14:32 +0200 Subject: Fixed normal bugs EXT-1628 ([BSI] - New IM system isn't sending typing notifications anymore) and EXT-1629 ([BSI] - add hint in IM window when Resident is typing) --HG-- branch : product-engine --- indra/newview/llimfloater.cpp | 135 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 129 insertions(+), 6 deletions(-) (limited to 'indra/newview/llimfloater.cpp') diff --git a/indra/newview/llimfloater.cpp b/indra/newview/llimfloater.cpp index a20b5ea66c..97af96847c 100644 --- a/indra/newview/llimfloater.cpp +++ b/indra/newview/llimfloater.cpp @@ -61,7 +61,14 @@ LLIMFloater::LLIMFloater(const LLUUID& session_id) mLastMessageIndex(-1), mDialog(IM_NOTHING_SPECIAL), mChatHistory(NULL), - mInputEditor(NULL), + mInputEditor(NULL), + mSavedTitle(), + mTypingStart(), + mShouldSendTypingState(false), + mMeTyping(false), + mOtherTyping(false), + mTypingTimer(), + mTypingTimeoutTimer(), mPositioned(false), mSessionInitialized(false) { @@ -95,6 +102,7 @@ void LLIMFloater::onFocusReceived() // virtual void LLIMFloater::onClose(bool app_quitting) { + setTyping(false); gIMMgr->leaveSession(mSessionID); } @@ -141,6 +149,7 @@ void LLIMFloater::onSendMsg( LLUICtrl* ctrl, void* userdata ) { LLIMFloater* self = (LLIMFloater*) userdata; self->sendMsg(); + self->setTyping(false); } void LLIMFloater::sendMsg() @@ -228,12 +237,27 @@ BOOL LLIMFloater::postBuild() LLLogChat::loadHistory(getTitle(), &chatFromLogFile, (void *)this); } + mTypingStart = LLTrans::getString("IM_typing_start_string"); + //*TODO if session is not initialized yet, add some sort of a warning message like "starting session...blablabla" //see LLFloaterIMPanel for how it is done (IB) return LLDockableFloater::postBuild(); } +// virtual +void LLIMFloater::draw() +{ + if ( mMeTyping ) + { + // Time out if user hasn't typed for a while. + if ( mTypingTimeoutTimer.getElapsedTimeF32() > LLAgent::TYPING_TIMEOUT_SECS ) + { + setTyping(false); + } + } + LLFloater::draw(); +} // static @@ -450,7 +474,7 @@ void LLIMFloater::onInputEditorFocusReceived( LLFocusableElement* caller, void* void LLIMFloater::onInputEditorFocusLost(LLFocusableElement* caller, void* userdata) { LLIMFloater* self = (LLIMFloater*) userdata; - self->setTyping(FALSE); + self->setTyping(false); } // static @@ -460,19 +484,118 @@ void LLIMFloater::onInputEditorKeystroke(LLLineEditor* caller, void* userdata) std::string text = self->mInputEditor->getText(); if (!text.empty()) { - self->setTyping(TRUE); + self->setTyping(true); } else { // Deleting all text counts as stopping typing. - self->setTyping(FALSE); + self->setTyping(false); + } +} + +void LLIMFloater::setTyping(bool typing) +{ + if ( typing ) + { + // Started or proceeded typing, reset the typing timeout timer + mTypingTimeoutTimer.reset(); + } + + if ( mMeTyping != typing ) + { + // Typing state is changed + mMeTyping = typing; + // So, should send current state + mShouldSendTypingState = true; + // In case typing is started, send state after some delay + mTypingTimer.reset(); + } + + // Don't want to send typing indicators to multiple people, potentially too + // much network traffic. Only send in person-to-person IMs. + if ( mShouldSendTypingState && mDialog == IM_NOTHING_SPECIAL ) + { + if ( mMeTyping ) + { + if ( mTypingTimer.getElapsedTimeF32() > 1.f ) + { + // Still typing, send 'start typing' notification + LLIMModel::instance().sendTypingState(mSessionID, mOtherParticipantUUID, TRUE); + mShouldSendTypingState = false; + } + } + else + { + // Send 'stop typing' notification immediately + LLIMModel::instance().sendTypingState(mSessionID, mOtherParticipantUUID, FALSE); + mShouldSendTypingState = false; + } + } + + LLIMSpeakerMgr* speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(mSessionID); + if (speaker_mgr) + speaker_mgr->setSpeakerTyping(gAgent.getID(), FALSE); + +} + +void LLIMFloater::processIMTyping(const LLIMInfo* im_info, BOOL typing) +{ + if ( typing ) + { + // other user started typing + addTypingIndicator(im_info); + } + else + { + // other user stopped typing + removeTypingIndicator(im_info); } } +void LLIMFloater::addTypingIndicator(const LLIMInfo* im_info) +{ + // We may have lost a "stop-typing" packet, don't add it twice + if ( im_info && !mOtherTyping ) + { + mOtherTyping = true; -//just a stub for now -void LLIMFloater::setTyping(BOOL typing) + // Create typing is started title string + LLUIString typing_start(mTypingStart); + typing_start.setArg("[NAME]", im_info->mName); + + // Save and set new title + mSavedTitle = getTitle(); + setTitle (typing_start); + + // Update speaker + LLIMSpeakerMgr* speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(mSessionID); + if ( speaker_mgr ) + { + speaker_mgr->setSpeakerTyping(im_info->mFromID, TRUE); + } + } +} + +void LLIMFloater::removeTypingIndicator(const LLIMInfo* im_info) { + if ( mOtherTyping ) + { + mOtherTyping = false; + + // Revert the title to saved one + setTitle(mSavedTitle); + + if ( im_info ) + { + // Update speaker + LLIMSpeakerMgr* speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(mSessionID); + if ( speaker_mgr ) + { + speaker_mgr->setSpeakerTyping(im_info->mFromID, FALSE); + } + } + + } } void LLIMFloater::chatFromLogFile(LLLogChat::ELogLineType type, std::string line, void* userdata) -- cgit v1.3 From e931d2d6f1a7d981bebf07b1d089fa4751096830 Mon Sep 17 00:00:00 2001 From: Igor Borovkov Date: Mon, 26 Oct 2009 17:33:28 +0200 Subject: IM refac.: replaced passing copies by passing references, moved some IMModel private, added documentation --HG-- branch : product-engine --- indra/newview/llimfloater.cpp | 3 ++- indra/newview/llimview.cpp | 27 ++++++++------------- indra/newview/llimview.h | 48 ++++++++++++++++++++++++++++++------- indra/newview/llviewerinventory.cpp | 1 - indra/newview/llviewerwindow.cpp | 1 - 5 files changed, 51 insertions(+), 29 deletions(-) (limited to 'indra/newview/llimfloater.cpp') diff --git a/indra/newview/llimfloater.cpp b/indra/newview/llimfloater.cpp index a20b5ea66c..57e3fba3f5 100644 --- a/indra/newview/llimfloater.cpp +++ b/indra/newview/llimfloater.cpp @@ -402,7 +402,8 @@ void LLIMFloater::sessionInitReplyReceived(const LLUUID& im_session_id) void LLIMFloater::updateMessages() { - std::list messages = LLIMModel::instance().getMessages(mSessionID, mLastMessageIndex+1); + std::list messages; + LLIMModel::instance().getMessages(mSessionID, messages, mLastMessageIndex+1); std::string agent_name; gCacheName->getFullName(gAgentID, agent_name); diff --git a/indra/newview/llimview.cpp b/indra/newview/llimview.cpp index 593f3a1e82..439f0b3d1e 100644 --- a/indra/newview/llimview.cpp +++ b/indra/newview/llimview.cpp @@ -272,7 +272,8 @@ void LLIMModel::testMessages() } -bool LLIMModel::newSession(LLUUID session_id, std::string name, EInstantMessage type, LLUUID other_participant_id, const std::vector& ids) +bool LLIMModel::newSession(const LLUUID& session_id, const std::string& name, const EInstantMessage& type, + const LLUUID& other_participant_id, const std::vector& ids) { if (is_in_map(sSessionsMap, session_id)) { @@ -289,7 +290,7 @@ bool LLIMModel::newSession(LLUUID session_id, std::string name, EInstantMessage } -bool LLIMModel::clearSession(LLUUID session_id) +bool LLIMModel::clearSession(const LLUUID& session_id) { if (sSessionsMap.find(session_id) == sSessionsMap.end()) return false; delete (sSessionsMap[session_id]); @@ -297,16 +298,13 @@ bool LLIMModel::clearSession(LLUUID session_id) return true; } -//*TODO remake it, instead of returing the list pass it as as parameter (IB) -std::list LLIMModel::getMessages(LLUUID session_id, int start_index) +void LLIMModel::getMessages(const LLUUID& session_id, std::list& messages, int start_index) { - std::list return_list; - LLIMSession* session = findIMSession(session_id); if (!session) { llwarns << "session " << session_id << "does not exist " << llendl; - return return_list; + return; } int i = session->mMsgs.size() - start_index; @@ -317,7 +315,7 @@ std::list LLIMModel::getMessages(LLUUID session_id, int start_index) { LLSD msg; msg = *iter; - return_list.push_back(*iter); + messages.push_back(*iter); i--; } @@ -327,14 +325,9 @@ std::list LLIMModel::getMessages(LLUUID session_id, int start_index) arg["session_id"] = session_id; arg["num_unread"] = 0; mNoUnreadMsgsSignal(arg); - - // TODO: in the future is there a more efficient way to return these - //of course there is - return as parameter (IB) - return return_list; - } -bool LLIMModel::addToHistory(LLUUID session_id, std::string from, LLUUID from_id, std::string utf8_text) { +bool LLIMModel::addToHistory(const LLUUID& session_id, const std::string& from, const LLUUID& from_id, const std::string& utf8_text) { LLIMSession* session = findIMSession(session_id); @@ -383,8 +376,8 @@ bool LLIMModel::logToFile(const LLUUID& session_id, const std::string& from, con return false; } -//*TODO add const qualifier and pass by references (IB) -bool LLIMModel::addMessage(LLUUID session_id, std::string from, LLUUID from_id, std::string utf8_text, bool log2file /* = true */) { +bool LLIMModel::addMessage(const LLUUID& session_id, const std::string& from, const LLUUID& from_id, + const std::string& utf8_text, bool log2file /* = true */) { LLIMSession* session = findIMSession(session_id); if (!session) @@ -506,7 +499,7 @@ void LLIMModel::sendTypingState(LLUUID session_id, LLUUID other_participant_id, gAgent.sendReliableMessage(); } -void LLIMModel::sendLeaveSession(LLUUID session_id, LLUUID other_participant_id) +void LLIMModel::sendLeaveSession(const LLUUID& session_id, const LLUUID& other_participant_id) { if(session_id.notNull()) { diff --git a/indra/newview/llimview.h b/indra/newview/llimview.h index 5ba54d30c3..e3d0a50557 100644 --- a/indra/newview/llimview.h +++ b/indra/newview/llimview.h @@ -104,18 +104,35 @@ public: boost::signals2::connection addNewMsgCallback( session_callback_t cb ) { return mNewMsgSignal.connect(cb); } boost::signals2::connection addNoUnreadMsgsCallback( session_callback_t cb ) { return mNoUnreadMsgsSignal.connect(cb); } - bool newSession(LLUUID session_id, std::string name, EInstantMessage type, LLUUID other_participant_id, + /** + * Create new session object in a model + */ + bool newSession(const LLUUID& session_id, const std::string& name, const EInstantMessage& type, const LLUUID& other_participant_id, const std::vector& ids = std::vector()); - bool clearSession(LLUUID session_id); - std::list getMessages(LLUUID session_id, int start_index = 0); - bool addMessage(LLUUID session_id, std::string from, LLUUID other_participant_id, std::string utf8_text, bool log2file = true); - bool addToHistory(LLUUID session_id, std::string from, LLUUID from_id, std::string utf8_text); + /** + * Remove all session data associated with a session specified by session_id + */ + bool clearSession(const LLUUID& session_id); - bool logToFile(const LLUUID& session_id, const std::string& from, const std::string& utf8_text); + /** + * Populate supplied std::list with messages starting from index specified by start_index + */ + void getMessages(const LLUUID& session_id, std::list& messages, int start_index = 0); - //used to get the name of the session, for use as the title - //currently just the other avatar name + /** + * Add a message to an IM Model - the message is saved in a message store associated with a session specified by session_id + * and also saved into a file if log2file is specified. + * It sends new message signal for each added message. + */ + bool addMessage(const LLUUID& session_id, const std::string& from, const LLUUID& other_participant_id, const std::string& utf8_text, bool log2file = true); + + /** + * Get a session's name. + * For a P2P chat - it's an avatar's name, + * For a group chat - it's a group's name + * For an ad-hoc chat - is received from the server and is in a from of " conference" + */ const std::string& getName(const LLUUID& session_id) const; /** @@ -150,7 +167,7 @@ public: */ LLIMSpeakerMgr* getSpeakerManager(const LLUUID& session_id) const; - static void sendLeaveSession(LLUUID session_id, LLUUID other_participant_id); + static void sendLeaveSession(const LLUUID& session_id, const LLUUID& other_participant_id); static bool sendStartSession(const LLUUID& temp_session_id, const LLUUID& other_participant_id, const std::vector& ids, EInstantMessage dialog); static void sendTypingState(LLUUID session_id, LLUUID other_participant_id, BOOL typing); @@ -158,6 +175,19 @@ public: const LLUUID& other_participant_id, EInstantMessage dialog); void testMessages(); + +private: + + /** + * Add message to a list of message associated with session specified by session_id + */ + bool addToHistory(const LLUUID& session_id, const std::string& from, const LLUUID& from_id, const std::string& utf8_text); + + /** + * Save an IM message into a file + */ + //*TODO should also save uuid of a sender + bool logToFile(const LLUUID& session_id, const std::string& from, const std::string& utf8_text); }; class LLIMSessionObserver diff --git a/indra/newview/llviewerinventory.cpp b/indra/newview/llviewerinventory.cpp index 57a4117d5d..366e5602bd 100644 --- a/indra/newview/llviewerinventory.cpp +++ b/indra/newview/llviewerinventory.cpp @@ -42,7 +42,6 @@ #include "llconsole.h" #include "llinventorymodel.h" #include "llnotify.h" -#include "llimview.h" #include "llgesturemgr.h" #include "llinventorybridge.h" diff --git a/indra/newview/llviewerwindow.cpp b/indra/newview/llviewerwindow.cpp index c659e58e47..f3c1cf191a 100644 --- a/indra/newview/llviewerwindow.cpp +++ b/indra/newview/llviewerwindow.cpp @@ -142,7 +142,6 @@ #include "llstatview.h" #include "llsurface.h" #include "llsurfacepatch.h" -#include "llimview.h" #include "lltexlayer.h" #include "lltextbox.h" #include "lltexturecache.h" -- cgit v1.3 From 62c8f55e3e49f55bc76dbcaba797ca02b3072c3f Mon Sep 17 00:00:00 2001 From: "Yuri Chebotarev ychebotarev@productengine.com" Date: Tue, 27 Oct 2009 11:28:46 +0200 Subject: working on EXT-1713,EXT-1712,EXT-1711,EXT-1709 EXT-1713 Nearby Chat: Objects have tooltips (???)(???) EXT-1712 Nearby Chat: change context menu for objects EXT-1711 Nearby Chat: context menu isn't applicable for system messages EXT-1709 Nearby Chat: Avatar context menu can be triggered on avatar's icon only initial commit - panel for message headers --HG-- branch : product-engine --- indra/newview/llchathistory.cpp | 227 +++++++++++++++++++++++++++++++++++++--- indra/newview/llchathistory.h | 7 +- indra/newview/llimfloater.cpp | 6 +- indra/newview/llnearbychat.cpp | 20 +--- 4 files changed, 220 insertions(+), 40 deletions(-) (limited to 'indra/newview/llimfloater.cpp') diff --git a/indra/newview/llchathistory.cpp b/indra/newview/llchathistory.cpp index ebf46a6e3f..8d16f8e1f9 100644 --- a/indra/newview/llchathistory.cpp +++ b/indra/newview/llchathistory.cpp @@ -38,9 +38,210 @@ #include "llscrollcontainer.h" #include "llavatariconctrl.h" +#include "llimview.h" +#include "llcallingcard.h" //for LLAvatarTracker +#include "llagentdata.h" +#include "llavataractions.h" +#include "lltrans.h" + static LLDefaultChildRegistry::Register r("chat_history"); static const std::string MESSAGE_USERNAME_DATE_SEPARATOR(" ----- "); +std::string formatCurrentTime() +{ + time_t utc_time; + utc_time = time_corrected(); + std::string timeStr ="["+ LLTrans::getString("TimeHour")+"]:[" + +LLTrans::getString("TimeMin")+"] "; + + LLSD substitution; + + substitution["datetime"] = (S32) utc_time; + LLStringUtil::format (timeStr, substitution); + + return timeStr; +} + +class LLChatHistoryHeader: public LLPanel +{ +public: + static LLChatHistoryHeader* createInstance(const std::string& file_name) + { + LLChatHistoryHeader* pInstance = new LLChatHistoryHeader; + LLUICtrlFactory::getInstance()->buildPanel(pInstance, file_name); + return pInstance; + } + + BOOL handleMouseUp(S32 x, S32 y, MASK mask) + { + return LLPanel::handleMouseUp(x,y,mask); + } + + 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 == "add") + { + std::string name; + name.assign(getFirstName()); + name.append(" "); + name.append(getLastName()); + + LLAvatarActions::requestFriendshipDialog(getAvatarId(), name); + } + else if (level == "remove") + { + LLAvatarActions::removeFriendDialog(getAvatarId()); + } + } + + BOOL postBuild() + { + LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; + + registrar.add("AvatarIcon.Action", boost::bind(&LLChatHistoryHeader::onAvatarIconContextMenuItemClicked, this, _2)); + + LLMenuGL* menu = LLUICtrlFactory::getInstance()->createFromFile("menu_avatar_icon.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); + + mPopupMenuHandleAvatar = menu->getHandle(); + + return LLPanel::postBuild(); + } + + bool pointInChild(const std::string& name,S32 x,S32 y) + { + LLUICtrl* child = findChild(name); + if(!child) + return false; + 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); + } + const LLUUID& getAvatarId () const { return mAvatarID;} + const std::string& getFirstName() const { return mFirstName; } + const std::string& getLastName () const { return mLastName; } + + void setup(const LLChat& chat) + { + mAvatarID = chat.mFromID; + mSourceType = chat.mSourceType; + gCacheName->get(mAvatarID, FALSE, boost::bind(&LLChatHistoryHeader::nameUpdatedCallback, this, _1, _2, _3, _4)); + if(chat.mFromID.isNull()) + { + mSourceType = CHAT_SOURCE_SYSTEM; + } + + LLTextBox* userName = getChild("user_name"); + + if(!chat.mFromName.empty()) + userName->setValue(chat.mFromName); + else + { + std::string SL = LLTrans::getString("SECOND_LIFE"); + userName->setValue(SL); + } + + LLTextBox* timeBox = getChild("time_box"); + timeBox->setValue(formatCurrentTime()); + + LLAvatarIconCtrl* icon = getChild("avatar_icon"); + + + if(!chat.mFromID.isNull()) + { + icon->setValue(chat.mFromID); + } + else + { + + } + + if(mSourceType != CHAT_SOURCE_AGENT) + icon->setToolTip(std::string("")); + } + + void nameUpdatedCallback(const LLUUID& id,const std::string& first,const std::string& last,BOOL is_group) + { + if (id != mAvatarID) + return; + mFirstName = first; + mLastName = last; + } +protected: + void showContextMenu(S32 x,S32 y) + { + if(mSourceType == CHAT_SOURCE_SYSTEM) + showSystemContextMenu(x,y); + if(mSourceType == CHAT_SOURCE_AGENT) + showAvatarContextMenu(x,y); + if(mSourceType == CHAT_SOURCE_OBJECT) + showObjectContextMenu(x,y); + } + + void showSystemContextMenu(S32 x,S32 y) + { + } + void showObjectContextMenu(S32 x,S32 y) + { + } + + void showAvatarContextMenu(S32 x,S32 y) + { + LLMenuGL* menu = (LLMenuGL*)mPopupMenuHandleAvatar.get(); + + if(menu) + { + bool is_friend = LLAvatarTracker::instance().getBuddyInfo(mAvatarID) != NULL; + + menu->setItemEnabled("Add Friend", !is_friend); + menu->setItemEnabled("Remove Friend", is_friend); + + if(gAgentID == mAvatarID) + { + menu->setItemEnabled("Add Friend", false); + menu->setItemEnabled("Send IM", false); + menu->setItemEnabled("Remove Friend", false); + } + + menu->buildDrawLabels(); + menu->updateParent(LLMenuGL::sMenuContainer); + LLMenuGL::showPopup(this, menu, x, y); + } + } + + + +protected: + LLHandle mPopupMenuHandleAvatar; + + LLUUID mAvatarID; + EChatSourceType mSourceType; + std::string mFirstName; + std::string mLastName; + +}; + + LLChatHistory::LLChatHistory(const LLChatHistory::Params& p) : LLTextEditor(p), mMessageHeaderFilename(p.message_header), @@ -78,35 +279,27 @@ LLView* LLChatHistory::getSeparator() return separator; } -LLView* LLChatHistory::getHeader(const LLUUID& avatar_id, std::string& from, std::string& time) +LLView* LLChatHistory::getHeader(const LLChat& chat) { - LLPanel* header = LLUICtrlFactory::getInstance()->createFromFile(mMessageHeaderFilename, NULL, LLPanel::child_registry_t::instance()); - LLTextBox* userName = header->getChild("user_name"); - userName->setValue(from); - LLTextBox* timeBox = header->getChild("time_box"); - timeBox->setValue(time); - if(!avatar_id.isNull()) - { - LLAvatarIconCtrl* icon = header->getChild("avatar_icon"); - icon->setValue(avatar_id); - } + LLChatHistoryHeader* header = LLChatHistoryHeader::createInstance(mMessageHeaderFilename); + header->setup(chat); return header; } -void LLChatHistory::appendWidgetMessage(const LLUUID& avatar_id, std::string& from, std::string& time, std::string& message, LLStyle::Params& style_params) +void LLChatHistory::appendWidgetMessage(const LLChat& chat, LLStyle::Params& style_params) { LLView* view = NULL; std::string view_text; - if (mLastFromName == from) + if (mLastFromName == chat.mFromName) { view = getSeparator(); view_text = " "; } else { - view = getHeader(avatar_id, from, time); - view_text = "\n" + from + MESSAGE_USERNAME_DATE_SEPARATOR + time; + view = getHeader(chat); + view_text = "\n" + chat.mFromName + MESSAGE_USERNAME_DATE_SEPARATOR + formatCurrentTime(); } //Prepare the rect for the view LLRect target_rect = getDocumentView()->getRect(); @@ -118,9 +311,9 @@ void LLChatHistory::appendWidgetMessage(const LLUUID& avatar_id, std::string& fr appendWidget(view, view_text, FALSE, TRUE, mLeftWidgetPad, 0); //Append the text message - appendText(message, TRUE, style_params); + appendText(chat.mText, TRUE, style_params); - mLastFromName = from; + mLastFromName = chat.mFromName; blockUndo(); setCursorAndScrollToEnd(); } diff --git a/indra/newview/llchathistory.h b/indra/newview/llchathistory.h index d6eccf896a..f13e974a9c 100644 --- a/indra/newview/llchathistory.h +++ b/indra/newview/llchathistory.h @@ -34,6 +34,7 @@ #define LLCHATHISTORY_H_ #include "lltexteditor.h" +#include "llchat.h" //Chat log widget allowing addition of a message as a widget class LLChatHistory : public LLTextEditor @@ -85,7 +86,7 @@ class LLChatHistory : public LLTextEditor * @param time time of a message. * @return pointer to LLView header object. */ - LLView* getHeader(const LLUUID& avatar_id, std::string& from, std::string& time); + LLView* getHeader(const LLChat& chat); public: ~LLChatHistory(); @@ -94,11 +95,11 @@ class LLChatHistory : public LLTextEditor * Appends a widget message. * If last user appended message, concurs with current user, * separator is added before the message, otherwise header is added. - * @param from owner of a message. + * @param chat - base chat message. * @param time time of a message. * @param message message itself. */ - void appendWidgetMessage(const LLUUID& avatar_id, std::string& from, std::string& time, std::string& message, LLStyle::Params& style_params); + void appendWidgetMessage(const LLChat& chat, LLStyle::Params& style_params); private: std::string mLastFromName; diff --git a/indra/newview/llimfloater.cpp b/indra/newview/llimfloater.cpp index a20b5ea66c..d0d176766d 100644 --- a/indra/newview/llimfloater.cpp +++ b/indra/newview/llimfloater.cpp @@ -428,7 +428,11 @@ void LLIMFloater::updateMessages() if (from == agent_name) from = LLTrans::getString("You"); - mChatHistory->appendWidgetMessage(from_id, from, time, message, style_params); + LLChat chat(message); + chat.mFromID = from_id; + chat.mFromName = from; + + mChatHistory->appendWidgetMessage(chat, style_params); mLastMessageIndex = msg["index"].asInteger(); } diff --git a/indra/newview/llnearbychat.cpp b/indra/newview/llnearbychat.cpp index bbab9944f3..8e297c5d95 100644 --- a/indra/newview/llnearbychat.cpp +++ b/indra/newview/llnearbychat.cpp @@ -170,22 +170,6 @@ LLColor4 nearbychat_get_text_color(const LLChat& chat) return text_color; } -std::string formatCurrentTime() -{ - time_t utc_time; - utc_time = time_corrected(); - std::string timeStr ="["+ LLTrans::getString("TimeHour")+"]:[" - +LLTrans::getString("TimeMin")+"] "; - - LLSD substitution; - - substitution["datetime"] = (S32) utc_time; - LLStringUtil::format (timeStr, substitution); - - return timeStr; -} - - void LLNearbyChat::add_timestamped_line(const LLChat& chat, const LLColor4& color) { S32 font_size = gSavedSettings.getS32("ChatFontSize"); @@ -210,16 +194,14 @@ void LLNearbyChat::add_timestamped_line(const LLChat& chat, const LLColor4& colo style_params.font(fontp); LLUUID uuid = chat.mFromID; std::string from = chat.mFromName; - std::string time = formatCurrentTime(); std::string message = chat.mText; - mChatHistory->appendWidgetMessage(uuid, from, time, message, style_params); + mChatHistory->appendWidgetMessage(chat, style_params); } void LLNearbyChat::addMessage(const LLChat& chat) { LLColor4 color = nearbychat_get_text_color(chat); - if (chat.mChatType == CHAT_TYPE_DEBUG_MSG) { LLFloaterScriptDebug::addScriptLine(chat.mText, -- cgit v1.3 From 6222149487d7e241ad7b009dae1f2fa6f69adc5d Mon Sep 17 00:00:00 2001 From: Igor Borovkov Date: Tue, 27 Oct 2009 13:16:44 +0200 Subject: IM refactoring: moved voice channel related classes to its own llvoicechannel.* files from dying llimpanel.* files --HG-- branch : product-engine --- indra/newview/CMakeLists.txt | 2 + indra/newview/llappviewer.cpp | 2 +- indra/newview/llfloaterchatterbox.cpp | 2 +- indra/newview/llfloatervoicedevicesettings.cpp | 2 +- indra/newview/llimfloater.cpp | 2 +- indra/newview/llimpanel.cpp | 830 +---------------------- indra/newview/llimpanel.h | 127 ---- indra/newview/llimview.cpp | 1 + indra/newview/llvoicechannel.cpp | 872 +++++++++++++++++++++++++ indra/newview/llvoicechannel.h | 167 +++++ indra/newview/llvoiceclient.cpp | 2 +- 11 files changed, 1051 insertions(+), 958 deletions(-) create mode 100644 indra/newview/llvoicechannel.cpp create mode 100644 indra/newview/llvoicechannel.h (limited to 'indra/newview/llimfloater.cpp') diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index dd3937a6ef..d81ce0c4db 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -484,6 +484,7 @@ set(viewer_SOURCE_FILES llvoclouds.cpp llvograss.cpp llvoground.cpp + llvoicechannel.cpp llvoiceclient.cpp llvoiceremotectrl.cpp llvoicevisualizer.cpp @@ -960,6 +961,7 @@ set(viewer_HEADER_FILES llvoclouds.h llvograss.h llvoground.h + llvoicechannel.h llvoiceclient.h llvoiceremotectrl.h llvoicevisualizer.h diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 923a66ee8e..2ff414344a 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -49,7 +49,6 @@ #include "llviewerstats.h" #include "llmd5.h" #include "llpumpio.h" -#include "llimpanel.h" #include "llmimetypes.h" #include "llslurl.h" #include "llstartup.h" @@ -76,6 +75,7 @@ #include "llteleporthistory.h" #include "lllocationhistory.h" #include "llfasttimerview.h" +#include "llvoicechannel.h" #include "llweb.h" #include "llsecondlifeurls.h" diff --git a/indra/newview/llfloaterchatterbox.cpp b/indra/newview/llfloaterchatterbox.cpp index dea656b0e4..fbf09207fe 100644 --- a/indra/newview/llfloaterchatterbox.cpp +++ b/indra/newview/llfloaterchatterbox.cpp @@ -42,7 +42,7 @@ #include "llfloaterfriends.h" #include "llfloatergroups.h" #include "llviewercontrol.h" -#include "llimview.h" +#include "llvoicechannel.h" #include "llimpanel.h" // diff --git a/indra/newview/llfloatervoicedevicesettings.cpp b/indra/newview/llfloatervoicedevicesettings.cpp index b64257b11d..aca9198f59 100644 --- a/indra/newview/llfloatervoicedevicesettings.cpp +++ b/indra/newview/llfloatervoicedevicesettings.cpp @@ -43,7 +43,7 @@ #include "llsliderctrl.h" #include "llviewercontrol.h" #include "llvoiceclient.h" -#include "llimpanel.h" +#include "llvoicechannel.h" // Library includes (after viewer) #include "lluictrlfactory.h" diff --git a/indra/newview/llimfloater.cpp b/indra/newview/llimfloater.cpp index 91e383eb14..0e9d7b070a 100644 --- a/indra/newview/llimfloater.cpp +++ b/indra/newview/llimfloater.cpp @@ -42,7 +42,6 @@ #include "llchiclet.h" #include "llfloaterchat.h" #include "llfloaterreg.h" -#include "llimview.h" #include "lllineeditor.h" #include "lllogchat.h" #include "llpanelimcontrolpanel.h" @@ -50,6 +49,7 @@ #include "lltrans.h" #include "llchathistory.h" #include "llviewerwindow.h" +#include "llvoicechannel.h" #include "lltransientfloatermgr.h" diff --git a/indra/newview/llimpanel.cpp b/indra/newview/llimpanel.cpp index 163984f740..2d8372db04 100644 --- a/indra/newview/llimpanel.cpp +++ b/indra/newview/llimpanel.cpp @@ -77,6 +77,7 @@ #include "llviewercontrol.h" #include "lluictrlfactory.h" #include "llviewerwindow.h" +#include "llvoicechannel.h" #include "lllogchat.h" #include "llweb.h" #include "llhttpclient.h" @@ -90,7 +91,6 @@ const S32 LINE_HEIGHT = 16; const S32 MIN_WIDTH = 200; const S32 MIN_HEIGHT = 130; -const U32 DEFAULT_RETRIES_COUNT = 3; // // Statics @@ -100,831 +100,6 @@ static std::string sTitleString = "Instant Message with [NAME]"; static std::string sTypingStartString = "[NAME]: ..."; static std::string sSessionStartString = "Starting session with [NAME] please wait."; -LLVoiceChannel::voice_channel_map_t LLVoiceChannel::sVoiceChannelMap; -LLVoiceChannel::voice_channel_map_uri_t LLVoiceChannel::sVoiceChannelURIMap; -LLVoiceChannel* LLVoiceChannel::sCurrentVoiceChannel = NULL; -LLVoiceChannel* LLVoiceChannel::sSuspendedVoiceChannel = NULL; - -BOOL LLVoiceChannel::sSuspended = FALSE; - - - -class LLVoiceCallCapResponder : public LLHTTPClient::Responder -{ -public: - LLVoiceCallCapResponder(const LLUUID& session_id) : mSessionID(session_id) {}; - - virtual void error(U32 status, const std::string& reason); // called with bad status codes - virtual void result(const LLSD& content); - -private: - LLUUID mSessionID; -}; - - -void LLVoiceCallCapResponder::error(U32 status, const std::string& reason) -{ - llwarns << "LLVoiceCallCapResponder::error(" - << status << ": " << reason << ")" - << llendl; - LLVoiceChannel* channelp = LLVoiceChannel::getChannelByID(mSessionID); - if ( channelp ) - { - if ( 403 == status ) - { - //403 == no ability - LLNotifications::instance().add( - "VoiceNotAllowed", - channelp->getNotifyArgs()); - } - else - { - LLNotifications::instance().add( - "VoiceCallGenericError", - channelp->getNotifyArgs()); - } - channelp->deactivate(); - } -} - -void LLVoiceCallCapResponder::result(const LLSD& content) -{ - LLVoiceChannel* channelp = LLVoiceChannel::getChannelByID(mSessionID); - if (channelp) - { - //*TODO: DEBUG SPAM - LLSD::map_const_iterator iter; - for(iter = content.beginMap(); iter != content.endMap(); ++iter) - { - llinfos << "LLVoiceCallCapResponder::result got " - << iter->first << llendl; - } - - channelp->setChannelInfo( - content["voice_credentials"]["channel_uri"].asString(), - content["voice_credentials"]["channel_credentials"].asString()); - } -} - -// -// LLVoiceChannel -// -LLVoiceChannel::LLVoiceChannel(const LLUUID& session_id, const std::string& session_name) : - mSessionID(session_id), - mState(STATE_NO_CHANNEL_INFO), - mSessionName(session_name), - mIgnoreNextSessionLeave(FALSE) -{ - mNotifyArgs["VOICE_CHANNEL_NAME"] = mSessionName; - - if (!sVoiceChannelMap.insert(std::make_pair(session_id, this)).second) - { - // a voice channel already exists for this session id, so this instance will be orphaned - // the end result should simply be the failure to make voice calls - llwarns << "Duplicate voice channels registered for session_id " << session_id << llendl; - } - - LLVoiceClient::getInstance()->addObserver(this); -} - -LLVoiceChannel::~LLVoiceChannel() -{ - // Don't use LLVoiceClient::getInstance() here -- this can get called during atexit() time and that singleton MAY have already been destroyed. - if(gVoiceClient) - { - gVoiceClient->removeObserver(this); - } - - sVoiceChannelMap.erase(mSessionID); - sVoiceChannelURIMap.erase(mURI); -} - -void LLVoiceChannel::setChannelInfo( - const std::string& uri, - const std::string& credentials) -{ - setURI(uri); - - mCredentials = credentials; - - if (mState == STATE_NO_CHANNEL_INFO) - { - if (mURI.empty()) - { - LLNotifications::instance().add("VoiceChannelJoinFailed", mNotifyArgs); - llwarns << "Received empty URI for channel " << mSessionName << llendl; - deactivate(); - } - else if (mCredentials.empty()) - { - LLNotifications::instance().add("VoiceChannelJoinFailed", mNotifyArgs); - llwarns << "Received empty credentials for channel " << mSessionName << llendl; - deactivate(); - } - else - { - setState(STATE_READY); - - // if we are supposed to be active, reconnect - // this will happen on initial connect, as we request credentials on first use - if (sCurrentVoiceChannel == this) - { - // just in case we got new channel info while active - // should move over to new channel - activate(); - } - } - } -} - -void LLVoiceChannel::onChange(EStatusType type, const std::string &channelURI, bool proximal) -{ - if (channelURI != mURI) - { - return; - } - - if (type < BEGIN_ERROR_STATUS) - { - handleStatusChange(type); - } - else - { - handleError(type); - } -} - -void LLVoiceChannel::handleStatusChange(EStatusType type) -{ - // status updates - switch(type) - { - case STATUS_LOGIN_RETRY: - //mLoginNotificationHandle = LLNotifyBox::showXml("VoiceLoginRetry")->getHandle(); - LLNotifications::instance().add("VoiceLoginRetry"); - break; - case STATUS_LOGGED_IN: - //if (!mLoginNotificationHandle.isDead()) - //{ - // LLNotifyBox* notifyp = (LLNotifyBox*)mLoginNotificationHandle.get(); - // if (notifyp) - // { - // notifyp->close(); - // } - // mLoginNotificationHandle.markDead(); - //} - break; - case STATUS_LEFT_CHANNEL: - if (callStarted() && !mIgnoreNextSessionLeave && !sSuspended) - { - // if forceably removed from channel - // update the UI and revert to default channel - LLNotifications::instance().add("VoiceChannelDisconnected", mNotifyArgs); - deactivate(); - } - mIgnoreNextSessionLeave = FALSE; - break; - case STATUS_JOINING: - if (callStarted()) - { - setState(STATE_RINGING); - } - break; - case STATUS_JOINED: - if (callStarted()) - { - setState(STATE_CONNECTED); - } - default: - break; - } -} - -// default behavior is to just deactivate channel -// derived classes provide specific error messages -void LLVoiceChannel::handleError(EStatusType type) -{ - deactivate(); - setState(STATE_ERROR); -} - -BOOL LLVoiceChannel::isActive() -{ - // only considered active when currently bound channel matches what our channel - return callStarted() && LLVoiceClient::getInstance()->getCurrentChannel() == mURI; -} - -BOOL LLVoiceChannel::callStarted() -{ - return mState >= STATE_CALL_STARTED; -} - -void LLVoiceChannel::deactivate() -{ - if (mState >= STATE_RINGING) - { - // ignore session leave event - mIgnoreNextSessionLeave = TRUE; - } - - if (callStarted()) - { - setState(STATE_HUNG_UP); - // mute the microphone if required when returning to the proximal channel - if (gSavedSettings.getBOOL("AutoDisengageMic") && sCurrentVoiceChannel == this) - { - gSavedSettings.setBOOL("PTTCurrentlyEnabled", true); - } - } - - if (sCurrentVoiceChannel == this) - { - // default channel is proximal channel - sCurrentVoiceChannel = LLVoiceChannelProximal::getInstance(); - sCurrentVoiceChannel->activate(); - } -} - -void LLVoiceChannel::activate() -{ - if (callStarted()) - { - return; - } - - // deactivate old channel and mark ourselves as the active one - if (sCurrentVoiceChannel != this) - { - // mark as current before deactivating the old channel to prevent - // activating the proximal channel between IM calls - LLVoiceChannel* old_channel = sCurrentVoiceChannel; - sCurrentVoiceChannel = this; - if (old_channel) - { - old_channel->deactivate(); - } - } - - if (mState == STATE_NO_CHANNEL_INFO) - { - // responsible for setting status to active - getChannelInfo(); - } - else - { - setState(STATE_CALL_STARTED); - } -} - -void LLVoiceChannel::getChannelInfo() -{ - // pretend we have everything we need - if (sCurrentVoiceChannel == this) - { - setState(STATE_CALL_STARTED); - } -} - -//static -LLVoiceChannel* LLVoiceChannel::getChannelByID(const LLUUID& session_id) -{ - voice_channel_map_t::iterator found_it = sVoiceChannelMap.find(session_id); - if (found_it == sVoiceChannelMap.end()) - { - return NULL; - } - else - { - return found_it->second; - } -} - -//static -LLVoiceChannel* LLVoiceChannel::getChannelByURI(std::string uri) -{ - voice_channel_map_uri_t::iterator found_it = sVoiceChannelURIMap.find(uri); - if (found_it == sVoiceChannelURIMap.end()) - { - return NULL; - } - else - { - return found_it->second; - } -} - -void LLVoiceChannel::updateSessionID(const LLUUID& new_session_id) -{ - sVoiceChannelMap.erase(sVoiceChannelMap.find(mSessionID)); - mSessionID = new_session_id; - sVoiceChannelMap.insert(std::make_pair(mSessionID, this)); -} - -void LLVoiceChannel::setURI(std::string uri) -{ - sVoiceChannelURIMap.erase(mURI); - mURI = uri; - sVoiceChannelURIMap.insert(std::make_pair(mURI, this)); -} - -void LLVoiceChannel::setState(EState state) -{ - switch(state) - { - case STATE_RINGING: - gIMMgr->addSystemMessage(mSessionID, "ringing", mNotifyArgs); - break; - case STATE_CONNECTED: - gIMMgr->addSystemMessage(mSessionID, "connected", mNotifyArgs); - break; - case STATE_HUNG_UP: - gIMMgr->addSystemMessage(mSessionID, "hang_up", mNotifyArgs); - break; - default: - break; - } - - mState = state; -} - -void LLVoiceChannel::toggleCallWindowIfNeeded(EState state) -{ - if (state == STATE_CONNECTED) - { - LLFloaterReg::showInstance("voice_call", mSessionID); - } - // By checking that current state is CONNECTED we make sure that the call window - // has been shown, hence there's something to hide. This helps when user presses - // the "End call" button right after initiating the call. - // *TODO: move this check to LLFloaterCall? - else if (state == STATE_HUNG_UP && mState == STATE_CONNECTED) - { - LLFloaterReg::hideInstance("voice_call", mSessionID); - } -} - -//static -void LLVoiceChannel::initClass() -{ - sCurrentVoiceChannel = LLVoiceChannelProximal::getInstance(); -} - - -//static -void LLVoiceChannel::suspend() -{ - if (!sSuspended) - { - sSuspendedVoiceChannel = sCurrentVoiceChannel; - sSuspended = TRUE; - } -} - -//static -void LLVoiceChannel::resume() -{ - if (sSuspended) - { - if (gVoiceClient->voiceEnabled()) - { - if (sSuspendedVoiceChannel) - { - sSuspendedVoiceChannel->activate(); - } - else - { - LLVoiceChannelProximal::getInstance()->activate(); - } - } - sSuspended = FALSE; - } -} - - -// -// LLVoiceChannelGroup -// - -LLVoiceChannelGroup::LLVoiceChannelGroup(const LLUUID& session_id, const std::string& session_name) : - LLVoiceChannel(session_id, session_name) -{ - mRetries = DEFAULT_RETRIES_COUNT; - mIsRetrying = FALSE; -} - -void LLVoiceChannelGroup::deactivate() -{ - if (callStarted()) - { - LLVoiceClient::getInstance()->leaveNonSpatialChannel(); - } - LLVoiceChannel::deactivate(); -} - -void LLVoiceChannelGroup::activate() -{ - if (callStarted()) return; - - LLVoiceChannel::activate(); - - if (callStarted()) - { - // we have the channel info, just need to use it now - LLVoiceClient::getInstance()->setNonSpatialChannel( - mURI, - mCredentials); - -#if 0 // *TODO - if (!gAgent.isInGroup(mSessionID)) // ad-hoc channel - { - // Add the party to the list of people with which we've recently interacted. - for (/*people in the chat*/) - LLRecentPeople::instance().add(buddy_id); - } -#endif - } -} - -void LLVoiceChannelGroup::getChannelInfo() -{ - LLViewerRegion* region = gAgent.getRegion(); - if (region) - { - std::string url = region->getCapability("ChatSessionRequest"); - LLSD data; - data["method"] = "call"; - data["session-id"] = mSessionID; - LLHTTPClient::post(url, - data, - new LLVoiceCallCapResponder(mSessionID)); - } -} - -void LLVoiceChannelGroup::setChannelInfo( - const std::string& uri, - const std::string& credentials) -{ - setURI(uri); - - mCredentials = credentials; - - if (mState == STATE_NO_CHANNEL_INFO) - { - if(!mURI.empty() && !mCredentials.empty()) - { - setState(STATE_READY); - - // if we are supposed to be active, reconnect - // this will happen on initial connect, as we request credentials on first use - if (sCurrentVoiceChannel == this) - { - // just in case we got new channel info while active - // should move over to new channel - activate(); - } - } - else - { - //*TODO: notify user - llwarns << "Received invalid credentials for channel " << mSessionName << llendl; - deactivate(); - } - } - else if ( mIsRetrying ) - { - // we have the channel info, just need to use it now - LLVoiceClient::getInstance()->setNonSpatialChannel( - mURI, - mCredentials); - } -} - -void LLVoiceChannelGroup::handleStatusChange(EStatusType type) -{ - // status updates - switch(type) - { - case STATUS_JOINED: - mRetries = 3; - mIsRetrying = FALSE; - default: - break; - } - - LLVoiceChannel::handleStatusChange(type); -} - -void LLVoiceChannelGroup::handleError(EStatusType status) -{ - std::string notify; - switch(status) - { - case ERROR_CHANNEL_LOCKED: - case ERROR_CHANNEL_FULL: - notify = "VoiceChannelFull"; - break; - case ERROR_NOT_AVAILABLE: - //clear URI and credentials - //set the state to be no info - //and activate - if ( mRetries > 0 ) - { - mRetries--; - mIsRetrying = TRUE; - mIgnoreNextSessionLeave = TRUE; - - getChannelInfo(); - return; - } - else - { - notify = "VoiceChannelJoinFailed"; - mRetries = DEFAULT_RETRIES_COUNT; - mIsRetrying = FALSE; - } - - break; - - case ERROR_UNKNOWN: - default: - break; - } - - // notification - if (!notify.empty()) - { - LLNotificationPtr notification = LLNotifications::instance().add(notify, mNotifyArgs); - // echo to im window - gIMMgr->addMessage(mSessionID, LLUUID::null, SYSTEM_FROM, notification->getMessage()); - } - - LLVoiceChannel::handleError(status); -} - -void LLVoiceChannelGroup::setState(EState state) -{ - // HACK: Open/close the call window if needed. - toggleCallWindowIfNeeded(state); - - switch(state) - { - case STATE_RINGING: - if ( !mIsRetrying ) - { - gIMMgr->addSystemMessage(mSessionID, "ringing", mNotifyArgs); - } - - mState = state; - break; - default: - LLVoiceChannel::setState(state); - } -} - -// -// LLVoiceChannelProximal -// -LLVoiceChannelProximal::LLVoiceChannelProximal() : - LLVoiceChannel(LLUUID::null, LLStringUtil::null) -{ - activate(); -} - -BOOL LLVoiceChannelProximal::isActive() -{ - return callStarted() && LLVoiceClient::getInstance()->inProximalChannel(); -} - -void LLVoiceChannelProximal::activate() -{ - if (callStarted()) return; - - LLVoiceChannel::activate(); - - if (callStarted()) - { - // this implicitly puts you back in the spatial channel - LLVoiceClient::getInstance()->leaveNonSpatialChannel(); - } -} - -void LLVoiceChannelProximal::onChange(EStatusType type, const std::string &channelURI, bool proximal) -{ - if (!proximal) - { - return; - } - - if (type < BEGIN_ERROR_STATUS) - { - handleStatusChange(type); - } - else - { - handleError(type); - } -} - -void LLVoiceChannelProximal::handleStatusChange(EStatusType status) -{ - // status updates - switch(status) - { - case STATUS_LEFT_CHANNEL: - // do not notify user when leaving proximal channel - return; - case STATUS_VOICE_DISABLED: - gIMMgr->addSystemMessage(LLUUID::null, "unavailable", mNotifyArgs); - return; - default: - break; - } - LLVoiceChannel::handleStatusChange(status); -} - - -void LLVoiceChannelProximal::handleError(EStatusType status) -{ - std::string notify; - switch(status) - { - case ERROR_CHANNEL_LOCKED: - case ERROR_CHANNEL_FULL: - notify = "ProximalVoiceChannelFull"; - break; - default: - break; - } - - // notification - if (!notify.empty()) - { - LLNotifications::instance().add(notify, mNotifyArgs); - } - - LLVoiceChannel::handleError(status); -} - -void LLVoiceChannelProximal::deactivate() -{ - if (callStarted()) - { - setState(STATE_HUNG_UP); - } -} - - -// -// LLVoiceChannelP2P -// -LLVoiceChannelP2P::LLVoiceChannelP2P(const LLUUID& session_id, const std::string& session_name, const LLUUID& other_user_id) : - LLVoiceChannelGroup(session_id, session_name), - mOtherUserID(other_user_id), - mReceivedCall(FALSE) -{ - // make sure URI reflects encoded version of other user's agent id - setURI(LLVoiceClient::getInstance()->sipURIFromID(other_user_id)); -} - -void LLVoiceChannelP2P::handleStatusChange(EStatusType type) -{ - // status updates - switch(type) - { - case STATUS_LEFT_CHANNEL: - if (callStarted() && !mIgnoreNextSessionLeave && !sSuspended) - { - if (mState == STATE_RINGING) - { - // other user declined call - LLNotifications::instance().add("P2PCallDeclined", mNotifyArgs); - } - else - { - // other user hung up - LLNotifications::instance().add("VoiceChannelDisconnectedP2P", mNotifyArgs); - } - deactivate(); - } - mIgnoreNextSessionLeave = FALSE; - return; - default: - break; - } - - LLVoiceChannel::handleStatusChange(type); -} - -void LLVoiceChannelP2P::handleError(EStatusType type) -{ - switch(type) - { - case ERROR_NOT_AVAILABLE: - LLNotifications::instance().add("P2PCallNoAnswer", mNotifyArgs); - break; - default: - break; - } - - LLVoiceChannel::handleError(type); -} - -void LLVoiceChannelP2P::activate() -{ - if (callStarted()) return; - - LLVoiceChannel::activate(); - - if (callStarted()) - { - // no session handle yet, we're starting the call - if (mSessionHandle.empty()) - { - mReceivedCall = FALSE; - LLVoiceClient::getInstance()->callUser(mOtherUserID); - } - // otherwise answering the call - else - { - LLVoiceClient::getInstance()->answerInvite(mSessionHandle); - - // using the session handle invalidates it. Clear it out here so we can't reuse it by accident. - mSessionHandle.clear(); - } - - // Add the party to the list of people with which we've recently interacted. - LLRecentPeople::instance().add(mOtherUserID); - } -} - -void LLVoiceChannelP2P::getChannelInfo() -{ - // pretend we have everything we need, since P2P doesn't use channel info - if (sCurrentVoiceChannel == this) - { - setState(STATE_CALL_STARTED); - } -} - -// receiving session from other user who initiated call -void LLVoiceChannelP2P::setSessionHandle(const std::string& handle, const std::string &inURI) -{ - BOOL needs_activate = FALSE; - if (callStarted()) - { - // defer to lower agent id when already active - if (mOtherUserID < gAgent.getID()) - { - // pretend we haven't started the call yet, so we can connect to this session instead - deactivate(); - needs_activate = TRUE; - } - else - { - // we are active and have priority, invite the other user again - // under the assumption they will join this new session - mSessionHandle.clear(); - LLVoiceClient::getInstance()->callUser(mOtherUserID); - return; - } - } - - mSessionHandle = handle; - - // The URI of a p2p session should always be the other end's SIP URI. - if(!inURI.empty()) - { - setURI(inURI); - } - else - { - setURI(LLVoiceClient::getInstance()->sipURIFromID(mOtherUserID)); - } - - mReceivedCall = TRUE; - - if (needs_activate) - { - activate(); - } -} - -void LLVoiceChannelP2P::setState(EState state) -{ - // HACK: Open/close the call window if needed. - toggleCallWindowIfNeeded(state); - - // you only "answer" voice invites in p2p mode - // so provide a special purpose message here - if (mReceivedCall && state == STATE_RINGING) - { - gIMMgr->addSystemMessage(mSessionID, "answering", mNotifyArgs); - mState = state; - return; - } - LLVoiceChannel::setState(state); -} - // // LLFloaterIMPanel @@ -1905,6 +1080,9 @@ bool LLFloaterIMPanel::onConfirmForceCloseError(const LLSD& notification, const session_id); if ( floaterp ) floaterp->closeFloater(FALSE); + + + } return false; } diff --git a/indra/newview/llimpanel.h b/indra/newview/llimpanel.h index 4e306c7fab..31b5c5c127 100644 --- a/indra/newview/llimpanel.h +++ b/indra/newview/llimpanel.h @@ -50,133 +50,6 @@ class LLIMSpeakerMgr; class LLPanelActiveSpeakers; class LLPanelChatControlPanel; -class LLVoiceChannel : public LLVoiceClientStatusObserver -{ -public: - typedef enum e_voice_channel_state - { - STATE_NO_CHANNEL_INFO, - STATE_ERROR, - STATE_HUNG_UP, - STATE_READY, - STATE_CALL_STARTED, - STATE_RINGING, - STATE_CONNECTED - } EState; - - LLVoiceChannel(const LLUUID& session_id, const std::string& session_name); - virtual ~LLVoiceChannel(); - - /*virtual*/ void onChange(EStatusType status, const std::string &channelURI, bool proximal); - - virtual void handleStatusChange(EStatusType status); - virtual void handleError(EStatusType status); - virtual void deactivate(); - virtual void activate(); - virtual void setChannelInfo( - const std::string& uri, - const std::string& credentials); - virtual void getChannelInfo(); - virtual BOOL isActive(); - virtual BOOL callStarted(); - const std::string& getSessionName() const { return mSessionName; } - - const LLUUID getSessionID() { return mSessionID; } - EState getState() { return mState; } - - void updateSessionID(const LLUUID& new_session_id); - const LLSD& getNotifyArgs() { return mNotifyArgs; } - - static LLVoiceChannel* getChannelByID(const LLUUID& session_id); - static LLVoiceChannel* getChannelByURI(std::string uri); - static LLVoiceChannel* getCurrentVoiceChannel() { return sCurrentVoiceChannel; } - static void initClass(); - - static void suspend(); - static void resume(); - -protected: - virtual void setState(EState state); - void toggleCallWindowIfNeeded(EState state); - void setURI(std::string uri); - - std::string mURI; - std::string mCredentials; - LLUUID mSessionID; - EState mState; - std::string mSessionName; - LLSD mNotifyArgs; - BOOL mIgnoreNextSessionLeave; - LLHandle mLoginNotificationHandle; - - typedef std::map voice_channel_map_t; - static voice_channel_map_t sVoiceChannelMap; - - typedef std::map voice_channel_map_uri_t; - static voice_channel_map_uri_t sVoiceChannelURIMap; - - static LLVoiceChannel* sCurrentVoiceChannel; - static LLVoiceChannel* sSuspendedVoiceChannel; - static BOOL sSuspended; -}; - -class LLVoiceChannelGroup : public LLVoiceChannel -{ -public: - LLVoiceChannelGroup(const LLUUID& session_id, const std::string& session_name); - - /*virtual*/ void handleStatusChange(EStatusType status); - /*virtual*/ void handleError(EStatusType status); - /*virtual*/ void activate(); - /*virtual*/ void deactivate(); - /*vritual*/ void setChannelInfo( - const std::string& uri, - const std::string& credentials); - /*virtual*/ void getChannelInfo(); - -protected: - virtual void setState(EState state); - -private: - U32 mRetries; - BOOL mIsRetrying; -}; - -class LLVoiceChannelProximal : public LLVoiceChannel, public LLSingleton -{ -public: - LLVoiceChannelProximal(); - - /*virtual*/ void onChange(EStatusType status, const std::string &channelURI, bool proximal); - /*virtual*/ void handleStatusChange(EStatusType status); - /*virtual*/ void handleError(EStatusType status); - /*virtual*/ BOOL isActive(); - /*virtual*/ void activate(); - /*virtual*/ void deactivate(); - -}; - -class LLVoiceChannelP2P : public LLVoiceChannelGroup -{ -public: - LLVoiceChannelP2P(const LLUUID& session_id, const std::string& session_name, const LLUUID& other_user_id); - - /*virtual*/ void handleStatusChange(EStatusType status); - /*virtual*/ void handleError(EStatusType status); - /*virtual*/ void activate(); - /*virtual*/ void getChannelInfo(); - - void setSessionHandle(const std::string& handle, const std::string &inURI); - -protected: - virtual void setState(EState state); - -private: - std::string mSessionHandle; - LLUUID mOtherUserID; - BOOL mReceivedCall; -}; - class LLFloaterIMPanel : public LLFloater { public: diff --git a/indra/newview/llimview.cpp b/indra/newview/llimview.cpp index 59cd9cec86..164da4136f 100644 --- a/indra/newview/llimview.cpp +++ b/indra/newview/llimview.cpp @@ -70,6 +70,7 @@ #include "llviewerwindow.h" #include "llnotify.h" #include "llviewerregion.h" +#include "llvoicechannel.h" #include "lltrans.h" #include "llrecentpeople.h" diff --git a/indra/newview/llvoicechannel.cpp b/indra/newview/llvoicechannel.cpp new file mode 100644 index 0000000000..96fcf61e62 --- /dev/null +++ b/indra/newview/llvoicechannel.cpp @@ -0,0 +1,872 @@ +/** + * @file llvoicechannel.cpp + * @brief Voice Channel related classes + * + * $LicenseInfo:firstyear=2001&license=viewergpl$ + * + * Copyright (c) 2001-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llagent.h" +#include "llfloaterreg.h" +#include "llimview.h" +#include "llnotifications.h" +#include "llpanel.h" +#include "llrecentpeople.h" +#include "llviewercontrol.h" +#include "llvoicechannel.h" + + +LLVoiceChannel::voice_channel_map_t LLVoiceChannel::sVoiceChannelMap; +LLVoiceChannel::voice_channel_map_uri_t LLVoiceChannel::sVoiceChannelURIMap; +LLVoiceChannel* LLVoiceChannel::sCurrentVoiceChannel = NULL; +LLVoiceChannel* LLVoiceChannel::sSuspendedVoiceChannel = NULL; + +BOOL LLVoiceChannel::sSuspended = FALSE; + +// +// Constants +// +const U32 DEFAULT_RETRIES_COUNT = 3; + + +class LLVoiceCallCapResponder : public LLHTTPClient::Responder +{ +public: + LLVoiceCallCapResponder(const LLUUID& session_id) : mSessionID(session_id) {}; + + virtual void error(U32 status, const std::string& reason); // called with bad status codes + virtual void result(const LLSD& content); + +private: + LLUUID mSessionID; +}; + + +void LLVoiceCallCapResponder::error(U32 status, const std::string& reason) +{ + llwarns << "LLVoiceCallCapResponder::error(" + << status << ": " << reason << ")" + << llendl; + LLVoiceChannel* channelp = LLVoiceChannel::getChannelByID(mSessionID); + if ( channelp ) + { + if ( 403 == status ) + { + //403 == no ability + LLNotifications::instance().add( + "VoiceNotAllowed", + channelp->getNotifyArgs()); + } + else + { + LLNotifications::instance().add( + "VoiceCallGenericError", + channelp->getNotifyArgs()); + } + channelp->deactivate(); + } +} + +void LLVoiceCallCapResponder::result(const LLSD& content) +{ + LLVoiceChannel* channelp = LLVoiceChannel::getChannelByID(mSessionID); + if (channelp) + { + //*TODO: DEBUG SPAM + LLSD::map_const_iterator iter; + for(iter = content.beginMap(); iter != content.endMap(); ++iter) + { + llinfos << "LLVoiceCallCapResponder::result got " + << iter->first << llendl; + } + + channelp->setChannelInfo( + content["voice_credentials"]["channel_uri"].asString(), + content["voice_credentials"]["channel_credentials"].asString()); + } +} + +// +// LLVoiceChannel +// +LLVoiceChannel::LLVoiceChannel(const LLUUID& session_id, const std::string& session_name) : + mSessionID(session_id), + mState(STATE_NO_CHANNEL_INFO), + mSessionName(session_name), + mIgnoreNextSessionLeave(FALSE) +{ + mNotifyArgs["VOICE_CHANNEL_NAME"] = mSessionName; + + if (!sVoiceChannelMap.insert(std::make_pair(session_id, this)).second) + { + // a voice channel already exists for this session id, so this instance will be orphaned + // the end result should simply be the failure to make voice calls + llwarns << "Duplicate voice channels registered for session_id " << session_id << llendl; + } + + LLVoiceClient::getInstance()->addObserver(this); +} + +LLVoiceChannel::~LLVoiceChannel() +{ + // Don't use LLVoiceClient::getInstance() here -- this can get called during atexit() time and that singleton MAY have already been destroyed. + if(gVoiceClient) + { + gVoiceClient->removeObserver(this); + } + + sVoiceChannelMap.erase(mSessionID); + sVoiceChannelURIMap.erase(mURI); +} + +void LLVoiceChannel::setChannelInfo( + const std::string& uri, + const std::string& credentials) +{ + setURI(uri); + + mCredentials = credentials; + + if (mState == STATE_NO_CHANNEL_INFO) + { + if (mURI.empty()) + { + LLNotifications::instance().add("VoiceChannelJoinFailed", mNotifyArgs); + llwarns << "Received empty URI for channel " << mSessionName << llendl; + deactivate(); + } + else if (mCredentials.empty()) + { + LLNotifications::instance().add("VoiceChannelJoinFailed", mNotifyArgs); + llwarns << "Received empty credentials for channel " << mSessionName << llendl; + deactivate(); + } + else + { + setState(STATE_READY); + + // if we are supposed to be active, reconnect + // this will happen on initial connect, as we request credentials on first use + if (sCurrentVoiceChannel == this) + { + // just in case we got new channel info while active + // should move over to new channel + activate(); + } + } + } +} + +void LLVoiceChannel::onChange(EStatusType type, const std::string &channelURI, bool proximal) +{ + if (channelURI != mURI) + { + return; + } + + if (type < BEGIN_ERROR_STATUS) + { + handleStatusChange(type); + } + else + { + handleError(type); + } +} + +void LLVoiceChannel::handleStatusChange(EStatusType type) +{ + // status updates + switch(type) + { + case STATUS_LOGIN_RETRY: + //mLoginNotificationHandle = LLNotifyBox::showXml("VoiceLoginRetry")->getHandle(); + LLNotifications::instance().add("VoiceLoginRetry"); + break; + case STATUS_LOGGED_IN: + //if (!mLoginNotificationHandle.isDead()) + //{ + // LLNotifyBox* notifyp = (LLNotifyBox*)mLoginNotificationHandle.get(); + // if (notifyp) + // { + // notifyp->close(); + // } + // mLoginNotificationHandle.markDead(); + //} + break; + case STATUS_LEFT_CHANNEL: + if (callStarted() && !mIgnoreNextSessionLeave && !sSuspended) + { + // if forceably removed from channel + // update the UI and revert to default channel + LLNotifications::instance().add("VoiceChannelDisconnected", mNotifyArgs); + deactivate(); + } + mIgnoreNextSessionLeave = FALSE; + break; + case STATUS_JOINING: + if (callStarted()) + { + setState(STATE_RINGING); + } + break; + case STATUS_JOINED: + if (callStarted()) + { + setState(STATE_CONNECTED); + } + default: + break; + } +} + +// default behavior is to just deactivate channel +// derived classes provide specific error messages +void LLVoiceChannel::handleError(EStatusType type) +{ + deactivate(); + setState(STATE_ERROR); +} + +BOOL LLVoiceChannel::isActive() +{ + // only considered active when currently bound channel matches what our channel + return callStarted() && LLVoiceClient::getInstance()->getCurrentChannel() == mURI; +} + +BOOL LLVoiceChannel::callStarted() +{ + return mState >= STATE_CALL_STARTED; +} + +void LLVoiceChannel::deactivate() +{ + if (mState >= STATE_RINGING) + { + // ignore session leave event + mIgnoreNextSessionLeave = TRUE; + } + + if (callStarted()) + { + setState(STATE_HUNG_UP); + // mute the microphone if required when returning to the proximal channel + if (gSavedSettings.getBOOL("AutoDisengageMic") && sCurrentVoiceChannel == this) + { + gSavedSettings.setBOOL("PTTCurrentlyEnabled", true); + } + } + + if (sCurrentVoiceChannel == this) + { + // default channel is proximal channel + sCurrentVoiceChannel = LLVoiceChannelProximal::getInstance(); + sCurrentVoiceChannel->activate(); + } +} + +void LLVoiceChannel::activate() +{ + if (callStarted()) + { + return; + } + + // deactivate old channel and mark ourselves as the active one + if (sCurrentVoiceChannel != this) + { + // mark as current before deactivating the old channel to prevent + // activating the proximal channel between IM calls + LLVoiceChannel* old_channel = sCurrentVoiceChannel; + sCurrentVoiceChannel = this; + if (old_channel) + { + old_channel->deactivate(); + } + } + + if (mState == STATE_NO_CHANNEL_INFO) + { + // responsible for setting status to active + getChannelInfo(); + } + else + { + setState(STATE_CALL_STARTED); + } +} + +void LLVoiceChannel::getChannelInfo() +{ + // pretend we have everything we need + if (sCurrentVoiceChannel == this) + { + setState(STATE_CALL_STARTED); + } +} + +//static +LLVoiceChannel* LLVoiceChannel::getChannelByID(const LLUUID& session_id) +{ + voice_channel_map_t::iterator found_it = sVoiceChannelMap.find(session_id); + if (found_it == sVoiceChannelMap.end()) + { + return NULL; + } + else + { + return found_it->second; + } +} + +//static +LLVoiceChannel* LLVoiceChannel::getChannelByURI(std::string uri) +{ + voice_channel_map_uri_t::iterator found_it = sVoiceChannelURIMap.find(uri); + if (found_it == sVoiceChannelURIMap.end()) + { + return NULL; + } + else + { + return found_it->second; + } +} + +void LLVoiceChannel::updateSessionID(const LLUUID& new_session_id) +{ + sVoiceChannelMap.erase(sVoiceChannelMap.find(mSessionID)); + mSessionID = new_session_id; + sVoiceChannelMap.insert(std::make_pair(mSessionID, this)); +} + +void LLVoiceChannel::setURI(std::string uri) +{ + sVoiceChannelURIMap.erase(mURI); + mURI = uri; + sVoiceChannelURIMap.insert(std::make_pair(mURI, this)); +} + +void LLVoiceChannel::setState(EState state) +{ + switch(state) + { + case STATE_RINGING: + gIMMgr->addSystemMessage(mSessionID, "ringing", mNotifyArgs); + break; + case STATE_CONNECTED: + gIMMgr->addSystemMessage(mSessionID, "connected", mNotifyArgs); + break; + case STATE_HUNG_UP: + gIMMgr->addSystemMessage(mSessionID, "hang_up", mNotifyArgs); + break; + default: + break; + } + + mState = state; +} + +void LLVoiceChannel::toggleCallWindowIfNeeded(EState state) +{ + if (state == STATE_CONNECTED) + { + LLFloaterReg::showInstance("voice_call", mSessionID); + } + // By checking that current state is CONNECTED we make sure that the call window + // has been shown, hence there's something to hide. This helps when user presses + // the "End call" button right after initiating the call. + // *TODO: move this check to LLFloaterCall? + else if (state == STATE_HUNG_UP && mState == STATE_CONNECTED) + { + LLFloaterReg::hideInstance("voice_call", mSessionID); + } +} + +//static +void LLVoiceChannel::initClass() +{ + sCurrentVoiceChannel = LLVoiceChannelProximal::getInstance(); +} + + +//static +void LLVoiceChannel::suspend() +{ + if (!sSuspended) + { + sSuspendedVoiceChannel = sCurrentVoiceChannel; + sSuspended = TRUE; + } +} + +//static +void LLVoiceChannel::resume() +{ + if (sSuspended) + { + if (gVoiceClient->voiceEnabled()) + { + if (sSuspendedVoiceChannel) + { + sSuspendedVoiceChannel->activate(); + } + else + { + LLVoiceChannelProximal::getInstance()->activate(); + } + } + sSuspended = FALSE; + } +} + + +// +// LLVoiceChannelGroup +// + +LLVoiceChannelGroup::LLVoiceChannelGroup(const LLUUID& session_id, const std::string& session_name) : + LLVoiceChannel(session_id, session_name) +{ + mRetries = DEFAULT_RETRIES_COUNT; + mIsRetrying = FALSE; +} + +void LLVoiceChannelGroup::deactivate() +{ + if (callStarted()) + { + LLVoiceClient::getInstance()->leaveNonSpatialChannel(); + } + LLVoiceChannel::deactivate(); +} + +void LLVoiceChannelGroup::activate() +{ + if (callStarted()) return; + + LLVoiceChannel::activate(); + + if (callStarted()) + { + // we have the channel info, just need to use it now + LLVoiceClient::getInstance()->setNonSpatialChannel( + mURI, + mCredentials); + +#if 0 // *TODO + if (!gAgent.isInGroup(mSessionID)) // ad-hoc channel + { + // Add the party to the list of people with which we've recently interacted. + for (/*people in the chat*/) + LLRecentPeople::instance().add(buddy_id); + } +#endif + } +} + +void LLVoiceChannelGroup::getChannelInfo() +{ + LLViewerRegion* region = gAgent.getRegion(); + if (region) + { + std::string url = region->getCapability("ChatSessionRequest"); + LLSD data; + data["method"] = "call"; + data["session-id"] = mSessionID; + LLHTTPClient::post(url, + data, + new LLVoiceCallCapResponder(mSessionID)); + } +} + +void LLVoiceChannelGroup::setChannelInfo( + const std::string& uri, + const std::string& credentials) +{ + setURI(uri); + + mCredentials = credentials; + + if (mState == STATE_NO_CHANNEL_INFO) + { + if(!mURI.empty() && !mCredentials.empty()) + { + setState(STATE_READY); + + // if we are supposed to be active, reconnect + // this will happen on initial connect, as we request credentials on first use + if (sCurrentVoiceChannel == this) + { + // just in case we got new channel info while active + // should move over to new channel + activate(); + } + } + else + { + //*TODO: notify user + llwarns << "Received invalid credentials for channel " << mSessionName << llendl; + deactivate(); + } + } + else if ( mIsRetrying ) + { + // we have the channel info, just need to use it now + LLVoiceClient::getInstance()->setNonSpatialChannel( + mURI, + mCredentials); + } +} + +void LLVoiceChannelGroup::handleStatusChange(EStatusType type) +{ + // status updates + switch(type) + { + case STATUS_JOINED: + mRetries = 3; + mIsRetrying = FALSE; + default: + break; + } + + LLVoiceChannel::handleStatusChange(type); +} + +void LLVoiceChannelGroup::handleError(EStatusType status) +{ + std::string notify; + switch(status) + { + case ERROR_CHANNEL_LOCKED: + case ERROR_CHANNEL_FULL: + notify = "VoiceChannelFull"; + break; + case ERROR_NOT_AVAILABLE: + //clear URI and credentials + //set the state to be no info + //and activate + if ( mRetries > 0 ) + { + mRetries--; + mIsRetrying = TRUE; + mIgnoreNextSessionLeave = TRUE; + + getChannelInfo(); + return; + } + else + { + notify = "VoiceChannelJoinFailed"; + mRetries = DEFAULT_RETRIES_COUNT; + mIsRetrying = FALSE; + } + + break; + + case ERROR_UNKNOWN: + default: + break; + } + + // notification + if (!notify.empty()) + { + LLNotificationPtr notification = LLNotifications::instance().add(notify, mNotifyArgs); + // echo to im window + gIMMgr->addMessage(mSessionID, LLUUID::null, SYSTEM_FROM, notification->getMessage()); + } + + LLVoiceChannel::handleError(status); +} + +void LLVoiceChannelGroup::setState(EState state) +{ + // HACK: Open/close the call window if needed. + toggleCallWindowIfNeeded(state); + + switch(state) + { + case STATE_RINGING: + if ( !mIsRetrying ) + { + gIMMgr->addSystemMessage(mSessionID, "ringing", mNotifyArgs); + } + + mState = state; + break; + default: + LLVoiceChannel::setState(state); + } +} + +// +// LLVoiceChannelProximal +// +LLVoiceChannelProximal::LLVoiceChannelProximal() : + LLVoiceChannel(LLUUID::null, LLStringUtil::null) +{ + activate(); +} + +BOOL LLVoiceChannelProximal::isActive() +{ + return callStarted() && LLVoiceClient::getInstance()->inProximalChannel(); +} + +void LLVoiceChannelProximal::activate() +{ + if (callStarted()) return; + + LLVoiceChannel::activate(); + + if (callStarted()) + { + // this implicitly puts you back in the spatial channel + LLVoiceClient::getInstance()->leaveNonSpatialChannel(); + } +} + +void LLVoiceChannelProximal::onChange(EStatusType type, const std::string &channelURI, bool proximal) +{ + if (!proximal) + { + return; + } + + if (type < BEGIN_ERROR_STATUS) + { + handleStatusChange(type); + } + else + { + handleError(type); + } +} + +void LLVoiceChannelProximal::handleStatusChange(EStatusType status) +{ + // status updates + switch(status) + { + case STATUS_LEFT_CHANNEL: + // do not notify user when leaving proximal channel + return; + case STATUS_VOICE_DISABLED: + gIMMgr->addSystemMessage(LLUUID::null, "unavailable", mNotifyArgs); + return; + default: + break; + } + LLVoiceChannel::handleStatusChange(status); +} + + +void LLVoiceChannelProximal::handleError(EStatusType status) +{ + std::string notify; + switch(status) + { + case ERROR_CHANNEL_LOCKED: + case ERROR_CHANNEL_FULL: + notify = "ProximalVoiceChannelFull"; + break; + default: + break; + } + + // notification + if (!notify.empty()) + { + LLNotifications::instance().add(notify, mNotifyArgs); + } + + LLVoiceChannel::handleError(status); +} + +void LLVoiceChannelProximal::deactivate() +{ + if (callStarted()) + { + setState(STATE_HUNG_UP); + } +} + + +// +// LLVoiceChannelP2P +// +LLVoiceChannelP2P::LLVoiceChannelP2P(const LLUUID& session_id, const std::string& session_name, const LLUUID& other_user_id) : + LLVoiceChannelGroup(session_id, session_name), + mOtherUserID(other_user_id), + mReceivedCall(FALSE) +{ + // make sure URI reflects encoded version of other user's agent id + setURI(LLVoiceClient::getInstance()->sipURIFromID(other_user_id)); +} + +void LLVoiceChannelP2P::handleStatusChange(EStatusType type) +{ + // status updates + switch(type) + { + case STATUS_LEFT_CHANNEL: + if (callStarted() && !mIgnoreNextSessionLeave && !sSuspended) + { + if (mState == STATE_RINGING) + { + // other user declined call + LLNotifications::instance().add("P2PCallDeclined", mNotifyArgs); + } + else + { + // other user hung up + LLNotifications::instance().add("VoiceChannelDisconnectedP2P", mNotifyArgs); + } + deactivate(); + } + mIgnoreNextSessionLeave = FALSE; + return; + default: + break; + } + + LLVoiceChannel::handleStatusChange(type); +} + +void LLVoiceChannelP2P::handleError(EStatusType type) +{ + switch(type) + { + case ERROR_NOT_AVAILABLE: + LLNotifications::instance().add("P2PCallNoAnswer", mNotifyArgs); + break; + default: + break; + } + + LLVoiceChannel::handleError(type); +} + +void LLVoiceChannelP2P::activate() +{ + if (callStarted()) return; + + LLVoiceChannel::activate(); + + if (callStarted()) + { + // no session handle yet, we're starting the call + if (mSessionHandle.empty()) + { + mReceivedCall = FALSE; + LLVoiceClient::getInstance()->callUser(mOtherUserID); + } + // otherwise answering the call + else + { + LLVoiceClient::getInstance()->answerInvite(mSessionHandle); + + // using the session handle invalidates it. Clear it out here so we can't reuse it by accident. + mSessionHandle.clear(); + } + + // Add the party to the list of people with which we've recently interacted. + LLRecentPeople::instance().add(mOtherUserID); + } +} + +void LLVoiceChannelP2P::getChannelInfo() +{ + // pretend we have everything we need, since P2P doesn't use channel info + if (sCurrentVoiceChannel == this) + { + setState(STATE_CALL_STARTED); + } +} + +// receiving session from other user who initiated call +void LLVoiceChannelP2P::setSessionHandle(const std::string& handle, const std::string &inURI) +{ + BOOL needs_activate = FALSE; + if (callStarted()) + { + // defer to lower agent id when already active + if (mOtherUserID < gAgent.getID()) + { + // pretend we haven't started the call yet, so we can connect to this session instead + deactivate(); + needs_activate = TRUE; + } + else + { + // we are active and have priority, invite the other user again + // under the assumption they will join this new session + mSessionHandle.clear(); + LLVoiceClient::getInstance()->callUser(mOtherUserID); + return; + } + } + + mSessionHandle = handle; + + // The URI of a p2p session should always be the other end's SIP URI. + if(!inURI.empty()) + { + setURI(inURI); + } + else + { + setURI(LLVoiceClient::getInstance()->sipURIFromID(mOtherUserID)); + } + + mReceivedCall = TRUE; + + if (needs_activate) + { + activate(); + } +} + +void LLVoiceChannelP2P::setState(EState state) +{ + // HACK: Open/close the call window if needed. + toggleCallWindowIfNeeded(state); + + // you only "answer" voice invites in p2p mode + // so provide a special purpose message here + if (mReceivedCall && state == STATE_RINGING) + { + gIMMgr->addSystemMessage(mSessionID, "answering", mNotifyArgs); + mState = state; + return; + } + LLVoiceChannel::setState(state); +} diff --git a/indra/newview/llvoicechannel.h b/indra/newview/llvoicechannel.h new file mode 100644 index 0000000000..4a9100fba6 --- /dev/null +++ b/indra/newview/llvoicechannel.h @@ -0,0 +1,167 @@ +/** + * @file llvoicechannel.h + * @brief Voice channel related classes + * + * $LicenseInfo:firstyear=2001&license=viewergpl$ + * + * Copyright (c) 2001-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LL_VOICECHANNEL_H +#define LL_VOICECHANNEL_H + +#include "llvoiceclient.h" + +class LLPanel; + +class LLVoiceChannel : public LLVoiceClientStatusObserver +{ +public: + typedef enum e_voice_channel_state + { + STATE_NO_CHANNEL_INFO, + STATE_ERROR, + STATE_HUNG_UP, + STATE_READY, + STATE_CALL_STARTED, + STATE_RINGING, + STATE_CONNECTED + } EState; + + LLVoiceChannel(const LLUUID& session_id, const std::string& session_name); + virtual ~LLVoiceChannel(); + + /*virtual*/ void onChange(EStatusType status, const std::string &channelURI, bool proximal); + + virtual void handleStatusChange(EStatusType status); + virtual void handleError(EStatusType status); + virtual void deactivate(); + virtual void activate(); + virtual void setChannelInfo( + const std::string& uri, + const std::string& credentials); + virtual void getChannelInfo(); + virtual BOOL isActive(); + virtual BOOL callStarted(); + const std::string& getSessionName() const { return mSessionName; } + + const LLUUID getSessionID() { return mSessionID; } + EState getState() { return mState; } + + void updateSessionID(const LLUUID& new_session_id); + const LLSD& getNotifyArgs() { return mNotifyArgs; } + + static LLVoiceChannel* getChannelByID(const LLUUID& session_id); + static LLVoiceChannel* getChannelByURI(std::string uri); + static LLVoiceChannel* getCurrentVoiceChannel() { return sCurrentVoiceChannel; } + static void initClass(); + + static void suspend(); + static void resume(); + +protected: + virtual void setState(EState state); + void toggleCallWindowIfNeeded(EState state); + void setURI(std::string uri); + + std::string mURI; + std::string mCredentials; + LLUUID mSessionID; + EState mState; + std::string mSessionName; + LLSD mNotifyArgs; + BOOL mIgnoreNextSessionLeave; + LLHandle mLoginNotificationHandle; + + typedef std::map voice_channel_map_t; + static voice_channel_map_t sVoiceChannelMap; + + typedef std::map voice_channel_map_uri_t; + static voice_channel_map_uri_t sVoiceChannelURIMap; + + static LLVoiceChannel* sCurrentVoiceChannel; + static LLVoiceChannel* sSuspendedVoiceChannel; + static BOOL sSuspended; +}; + +class LLVoiceChannelGroup : public LLVoiceChannel +{ +public: + LLVoiceChannelGroup(const LLUUID& session_id, const std::string& session_name); + + /*virtual*/ void handleStatusChange(EStatusType status); + /*virtual*/ void handleError(EStatusType status); + /*virtual*/ void activate(); + /*virtual*/ void deactivate(); + /*vritual*/ void setChannelInfo( + const std::string& uri, + const std::string& credentials); + /*virtual*/ void getChannelInfo(); + +protected: + virtual void setState(EState state); + +private: + U32 mRetries; + BOOL mIsRetrying; +}; + +class LLVoiceChannelProximal : public LLVoiceChannel, public LLSingleton +{ +public: + LLVoiceChannelProximal(); + + /*virtual*/ void onChange(EStatusType status, const std::string &channelURI, bool proximal); + /*virtual*/ void handleStatusChange(EStatusType status); + /*virtual*/ void handleError(EStatusType status); + /*virtual*/ BOOL isActive(); + /*virtual*/ void activate(); + /*virtual*/ void deactivate(); + +}; + +class LLVoiceChannelP2P : public LLVoiceChannelGroup +{ +public: + LLVoiceChannelP2P(const LLUUID& session_id, const std::string& session_name, const LLUUID& other_user_id); + + /*virtual*/ void handleStatusChange(EStatusType status); + /*virtual*/ void handleError(EStatusType status); + /*virtual*/ void activate(); + /*virtual*/ void getChannelInfo(); + + void setSessionHandle(const std::string& handle, const std::string &inURI); + +protected: + virtual void setState(EState state); + +private: + std::string mSessionHandle; + LLUUID mOtherUserID; + BOOL mReceivedCall; +}; + +#endif // LL_VOICECHANNEL_H diff --git a/indra/newview/llvoiceclient.cpp b/indra/newview/llvoiceclient.cpp index 02f63a848b..2834284a9b 100644 --- a/indra/newview/llvoiceclient.cpp +++ b/indra/newview/llvoiceclient.cpp @@ -57,13 +57,13 @@ #include "llagent.h" #include "llcachename.h" #include "llimview.h" // for LLIMMgr -#include "llimpanel.h" // for LLVoiceChannel #include "llparcel.h" #include "llviewerparcelmgr.h" #include "llfirstuse.h" #include "llviewerwindow.h" #include "llviewercamera.h" #include "llvoavatarself.h" +#include "llvoicechannel.h" #include "llfloaterfriends.h" //VIVOX, inorder to refresh communicate panel #include "llfloaterchat.h" // for LLFloaterChat::addChat() -- cgit v1.3 From 3eb7f84b10832d77d8765ea9550108c67c462bcb Mon Sep 17 00:00:00 2001 From: Igor Borovkov Date: Tue, 27 Oct 2009 18:01:41 +0200 Subject: IM: implemented task EXT-1905 (Add "Call" and "End Call" functionality to new IIM Floater) --HG-- branch : product-engine --- indra/newview/llimfloater.cpp | 7 +- indra/newview/llimview.cpp | 8 ++- indra/newview/llimview.h | 4 ++ indra/newview/llpanelimcontrolpanel.cpp | 80 +++++++++++++++++----- indra/newview/llpanelimcontrolpanel.h | 20 ++++-- .../default/xui/en/panel_group_control_panel.xml | 8 ++- .../default/xui/en/panel_im_control_panel.xml | 9 ++- 7 files changed, 107 insertions(+), 29 deletions(-) (limited to 'indra/newview/llimfloater.cpp') diff --git a/indra/newview/llimfloater.cpp b/indra/newview/llimfloater.cpp index 0e9d7b070a..9e92e2f490 100644 --- a/indra/newview/llimfloater.cpp +++ b/indra/newview/llimfloater.cpp @@ -202,9 +202,10 @@ BOOL LLIMFloater::postBuild() if (other_party_id.notNull()) { mOtherParticipantUUID = other_party_id; - mControlPanel->setID(mOtherParticipantUUID); } + mControlPanel->setSessionId(mSessionID); + LLButton* slide_left = getChild("slide_left_btn"); slide_left->setVisible(mControlPanel->getVisible()); slide_left->setClickedCallback(boost::bind(&LLIMFloater::onSlide, this)); @@ -248,6 +249,8 @@ BOOL LLIMFloater::postBuild() // virtual void LLIMFloater::draw() { + + if ( mMeTyping ) { // Time out if user hasn't typed for a while. @@ -403,10 +406,12 @@ void LLIMFloater::sessionInitReplyReceived(const LLUUID& im_session_id) { mSessionInitialized = true; + //will be different only for an ad-hoc im session if (mSessionID != im_session_id) { mSessionID = im_session_id; setKey(im_session_id); + mControlPanel->setSessionId(im_session_id); } //*TODO here we should remove "starting session..." warning message if we added it in postBuild() (IB) diff --git a/indra/newview/llimview.cpp b/indra/newview/llimview.cpp index 164da4136f..8792713423 100644 --- a/indra/newview/llimview.cpp +++ b/indra/newview/llimview.cpp @@ -145,7 +145,8 @@ LLIMModel::LLIMSession::LLIMSession(const LLUUID& session_id, const std::string& mInitialTargetIDs(ids), mVoiceChannel(NULL), mSpeakers(NULL), - mSessionInitialized(false) + mSessionInitialized(false), + mCallBackEnabled(true) { if (IM_NOTHING_SPECIAL == type || IM_SESSION_P2P_INVITE == type) { @@ -169,6 +170,11 @@ LLIMModel::LLIMSession::LLIMSession(const LLUUID& session_id, const std::string& //so we're already initialized mSessionInitialized = true; } + + if (IM_NOTHING_SPECIAL == type) + { + mCallBackEnabled = LLVoiceClient::getInstance()->isSessionCallBackPossible(mSessionID); + } } LLIMModel::LLIMSession::~LLIMSession() diff --git a/indra/newview/llimview.h b/indra/newview/llimview.h index e3d0a50557..f83520af82 100644 --- a/indra/newview/llimview.h +++ b/indra/newview/llimview.h @@ -70,6 +70,10 @@ public: LLIMSpeakerMgr* mSpeakers; bool mSessionInitialized; + + //true if calling back the session URI after the session has closed is possible. + //Currently this will be false only for PSTN P2P calls. + bool mCallBackEnabled; }; diff --git a/indra/newview/llpanelimcontrolpanel.cpp b/indra/newview/llpanelimcontrolpanel.cpp index 6eed956eb8..6678a3a460 100644 --- a/indra/newview/llpanelimcontrolpanel.cpp +++ b/indra/newview/llpanelimcontrolpanel.cpp @@ -34,6 +34,7 @@ #include "llpanelimcontrolpanel.h" +#include "llagent.h" #include "llavataractions.h" #include "llavatariconctrl.h" #include "llbutton.h" @@ -41,6 +42,53 @@ #include "llavatarlist.h" #include "llparticipantlist.h" #include "llimview.h" +#include "llvoicechannel.h" + +void LLPanelChatControlPanel::onCallButtonClicked() +{ + gIMMgr->startCall(mSessionId); +} + +void LLPanelChatControlPanel::onEndCallButtonClicked() +{ + gIMMgr->endCall(mSessionId); +} + +BOOL LLPanelChatControlPanel::postBuild() +{ + childSetAction("call_btn", boost::bind(&LLPanelChatControlPanel::onCallButtonClicked, this)); + childSetAction("end_call_btn", boost::bind(&LLPanelChatControlPanel::onEndCallButtonClicked, this)); + + return TRUE; +} + +void LLPanelChatControlPanel::draw() +{ + // hide/show start call and end call buttons + bool voice_enabled = LLVoiceClient::voiceEnabled(); + + LLIMModel::LLIMSession* session = LLIMModel::getInstance()->findIMSession(mSessionId); + if (!session) return; + + LLVoiceChannel* voice_channel = session->mVoiceChannel; + if (voice_channel && voice_enabled) + { + childSetVisible("end_call_btn", voice_channel->getState() >= LLVoiceChannel::STATE_CALL_STARTED); + childSetVisible("call_btn", voice_channel->getState() < LLVoiceChannel::STATE_CALL_STARTED); + } + + bool session_initialized = session->mSessionInitialized; + bool callback_enabled = session->mCallBackEnabled; + LLViewerRegion* region = gAgent.getRegion(); + + BOOL enable_connect = (region && region->getCapability("ChatSessionRequest") != "") + && session_initialized + && voice_enabled + && callback_enabled; + childSetEnabled("call_btn", enable_connect); + + LLPanel::draw(); +} LLPanelIMControlPanel::LLPanelIMControlPanel() { @@ -54,11 +102,11 @@ BOOL LLPanelIMControlPanel::postBuild() { childSetAction("view_profile_btn", boost::bind(&LLPanelIMControlPanel::onViewProfileButtonClicked, this)); childSetAction("add_friend_btn", boost::bind(&LLPanelIMControlPanel::onAddFriendButtonClicked, this)); - childSetAction("call_btn", boost::bind(&LLPanelIMControlPanel::onCallButtonClicked, this)); + childSetAction("share_btn", boost::bind(&LLPanelIMControlPanel::onShareButtonClicked, this)); childSetEnabled("add_friend_btn", !LLAvatarActions::isFriend(getChild("avatar_icon")->getAvatarId())); - - return TRUE; + + return LLPanelChatControlPanel::postBuild(); } void LLPanelIMControlPanel::onViewProfileButtonClicked() @@ -73,21 +121,20 @@ void LLPanelIMControlPanel::onAddFriendButtonClicked() LLAvatarActions::requestFriendshipDialog(avatar_icon->getAvatarId(), full_name); } -void LLPanelIMControlPanel::onCallButtonClicked() -{ - // *TODO: Implement -} - void LLPanelIMControlPanel::onShareButtonClicked() { // *TODO: Implement } -void LLPanelIMControlPanel::setID(const LLUUID& avatar_id) +void LLPanelIMControlPanel::setSessionId(const LLUUID& session_id) { + LLPanelChatControlPanel::setSessionId(session_id); + + LLUUID avatar_id = LLIMModel::getInstance()->getOtherParticipantID(session_id); + // Disable "Add friend" button for friends. childSetEnabled("add_friend_btn", !LLAvatarActions::isFriend(avatar_id)); - + getChild("avatar_icon")->setValue(avatar_id); } @@ -100,12 +147,11 @@ LLPanelGroupControlPanel::LLPanelGroupControlPanel(const LLUUID& session_id) BOOL LLPanelGroupControlPanel::postBuild() { childSetAction("group_info_btn", boost::bind(&LLPanelGroupControlPanel::onGroupInfoButtonClicked, this)); - childSetAction("call_btn", boost::bind(&LLPanelGroupControlPanel::onCallButtonClicked, this)); mAvatarList = getChild("speakers_list"); mParticipantList = new LLParticipantList(mSpeakerManager, mAvatarList); - return TRUE; + return LLPanelChatControlPanel::postBuild(); } LLPanelGroupControlPanel::~LLPanelGroupControlPanel() @@ -127,13 +173,9 @@ void LLPanelGroupControlPanel::onGroupInfoButtonClicked() } -void LLPanelGroupControlPanel::onCallButtonClicked() +void LLPanelGroupControlPanel::setSessionId(const LLUUID& session_id) { - // *TODO: Implement -} - + LLPanelChatControlPanel::setSessionId(session_id); -void LLPanelGroupControlPanel::setID(const LLUUID& id) -{ - mGroupID = id; + mGroupID = LLIMModel::getInstance()->getOtherParticipantID(session_id); } diff --git a/indra/newview/llpanelimcontrolpanel.h b/indra/newview/llpanelimcontrolpanel.h index 138b1630c4..00c96cf31a 100644 --- a/indra/newview/llpanelimcontrolpanel.h +++ b/indra/newview/llpanelimcontrolpanel.h @@ -45,8 +45,16 @@ public: LLPanelChatControlPanel() {}; ~LLPanelChatControlPanel() {}; - // sets the group or avatar UUID - virtual void setID(const LLUUID& avatar_id)= 0; + virtual BOOL postBuild(); + virtual void draw(); + + void onCallButtonClicked(); + void onEndCallButtonClicked(); + + virtual void setSessionId(const LLUUID& session_id) { mSessionId = session_id; } + +private: + LLUUID mSessionId; }; @@ -58,13 +66,14 @@ public: BOOL postBuild(); - void setID(const LLUUID& avatar_id); + void setSessionId(const LLUUID& session_id); private: void onViewProfileButtonClicked(); void onAddFriendButtonClicked(); - void onCallButtonClicked(); void onShareButtonClicked(); + + LLUUID mAvatarID; }; @@ -76,12 +85,11 @@ public: BOOL postBuild(); - void setID(const LLUUID& id); + void setSessionId(const LLUUID& session_id); /*virtual*/ void draw(); private: void onGroupInfoButtonClicked(); - void onCallButtonClicked(); LLUUID mGroupID; LLSpeakerMgr* mSpeakerManager; diff --git a/indra/newview/skins/default/xui/en/panel_group_control_panel.xml b/indra/newview/skins/default/xui/en/panel_group_control_panel.xml index 9767a673f6..e1f18d8b7d 100644 --- a/indra/newview/skins/default/xui/en/panel_group_control_panel.xml +++ b/indra/newview/skins/default/xui/en/panel_group_control_panel.xml @@ -7,7 +7,7 @@ +