diff options
Diffstat (limited to 'indra/newview/llimfloater.cpp')
-rw-r--r-- | indra/newview/llimfloater.cpp | 850 |
1 files changed, 736 insertions, 114 deletions
diff --git a/indra/newview/llimfloater.cpp b/indra/newview/llimfloater.cpp index a20b5ea66c..3ec8d11fb0 100644 --- a/indra/newview/llimfloater.cpp +++ b/indra/newview/llimfloater.cpp @@ -34,24 +34,32 @@ #include "llimfloater.h" +#include "llnotificationsutil.h" + #include "llagent.h" #include "llappviewer.h" #include "llbutton.h" #include "llbottomtray.h" #include "llchannelmanager.h" #include "llchiclet.h" -#include "llfloaterchat.h" #include "llfloaterreg.h" -#include "llimview.h" +#include "llimfloatercontainer.h" // to replace separate IM Floaters with multifloater container +#include "llinventoryfunctions.h" +#include "lllayoutstack.h" #include "lllineeditor.h" #include "lllogchat.h" #include "llpanelimcontrolpanel.h" #include "llscreenchannel.h" +#include "llsyswellwindow.h" #include "lltrans.h" #include "llchathistory.h" #include "llviewerwindow.h" +#include "llvoicechannel.h" #include "lltransientfloatermgr.h" +#include "llinventorymodel.h" +#include "llrootview.h" +#include "llspeakers.h" LLIMFloater::LLIMFloater(const LLUUID& session_id) @@ -61,7 +69,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) { @@ -71,46 +86,78 @@ LLIMFloater::LLIMFloater(const LLUUID& session_id) mSessionInitialized = im_session->mSessionInitialized; mDialog = im_session->mType; - if (IM_NOTHING_SPECIAL == mDialog || IM_SESSION_P2P_INVITE == mDialog) - { + switch(mDialog){ + case IM_NOTHING_SPECIAL: + case IM_SESSION_P2P_INVITE: mFactoryMap["panel_im_control_panel"] = LLCallbackMap(createPanelIMControl, this); - } - else - { + break; + case IM_SESSION_CONFERENCE_START: + mFactoryMap["panel_im_control_panel"] = LLCallbackMap(createPanelAdHocControl, this); + break; + case IM_SESSION_GROUP_START: mFactoryMap["panel_im_control_panel"] = LLCallbackMap(createPanelGroupControl, this); + break; + case IM_SESSION_INVITE: + if (gAgent.isInGroup(mSessionID)) + { + mFactoryMap["panel_im_control_panel"] = LLCallbackMap(createPanelGroupControl, this); + } + else + { + mFactoryMap["panel_im_control_panel"] = LLCallbackMap(createPanelAdHocControl, this); + } + break; + default: break; } } + setOverlapsScreenChannel(true); + + LLTransientFloaterMgr::getInstance()->addControlView(LLTransientFloaterMgr::IM, this); + + setDocked(true); } void LLIMFloater::onFocusLost() { LLIMModel::getInstance()->resetActiveSessionID(); + + LLBottomTray::getInstance()->getChicletPanel()->setChicletToggleState(mSessionID, false); } void LLIMFloater::onFocusReceived() { LLIMModel::getInstance()->setActiveSessionID(mSessionID); + + LLBottomTray::getInstance()->getChicletPanel()->setChicletToggleState(mSessionID, true); + + if (getVisible()) + { + LLIMModel::instance().sendNoUnreadMessages(mSessionID); + } } // virtual void LLIMFloater::onClose(bool app_quitting) { + setTyping(false); + + // The source of much argument and design thrashing + // Should the window hide or the session close when the X is clicked? + // + // Last change: + // EXT-3516 X Button should end IM session, _ button should hide gIMMgr->leaveSession(mSessionID); } /* static */ void LLIMFloater::newIMCallback(const LLSD& data){ - if (data["num_unread"].asInteger() > 0) + if (data["num_unread"].asInteger() > 0 || data["from_id"].asUUID().isNull()) { LLUUID session_id = data["session_id"].asUUID(); LLIMFloater* floater = LLFloaterReg::findTypedInstance<LLIMFloater>("impanel", session_id); - if (floater == NULL) - { - llwarns << "new_im_callback for non-existent session_id " << session_id << llendl; - return; - } + if (floater == NULL) return; // update if visible, otherwise will be updated when opened if (floater->getVisible()) @@ -141,6 +188,7 @@ void LLIMFloater::onSendMsg( LLUICtrl* ctrl, void* userdata ) { LLIMFloater* self = (LLIMFloater*) userdata; self->sendMsg(); + self->setTyping(false); } void LLIMFloater::sendMsg() @@ -184,18 +232,27 @@ void LLIMFloater::sendMsg() LLIMFloater::~LLIMFloater() { + LLTransientFloaterMgr::getInstance()->removeControlView(LLTransientFloaterMgr::IM, this); } //virtual BOOL LLIMFloater::postBuild() { + // User-resizable control panels in P2P sessions look ugly (EXT-3470). + if (mDialog == IM_NOTHING_SPECIAL || mDialog == IM_SESSION_P2P_INVITE) + { + getChild<LLLayoutStack>("im_panels")->setPanelUserResize("panel_im_control_panel", FALSE); + } + const LLUUID& other_party_id = LLIMModel::getInstance()->getOtherParticipantID(mSessionID); if (other_party_id.notNull()) { mOtherParticipantUUID = other_party_id; - mControlPanel->setID(mOtherParticipantUUID); } + mControlPanel->setSessionId(mSessionID); + mControlPanel->setVisible(gSavedSettings.getBOOL("IMShowControlPanel")); + LLButton* slide_left = getChild<LLButton>("slide_left_btn"); slide_left->setVisible(mControlPanel->getVisible()); slide_left->setClickedCallback(boost::bind(&LLIMFloater::onSlide, this)); @@ -209,31 +266,65 @@ BOOL LLIMFloater::postBuild() // enable line history support for instant message bar mInputEditor->setEnableLineHistory(TRUE); + mInputEditor->setFocusReceivedCallback( boost::bind(onInputEditorFocusReceived, _1, this) ); mInputEditor->setFocusLostCallback( boost::bind(onInputEditorFocusLost, _1, this) ); mInputEditor->setKeystrokeCallback( onInputEditorKeystroke, this ); mInputEditor->setCommitOnFocusLost( FALSE ); mInputEditor->setRevertOnEsc( FALSE ); mInputEditor->setReplaceNewlinesWithSpaces( FALSE ); + mInputEditor->setPassDelete( TRUE ); + + std::string session_name(LLIMModel::instance().getName(mSessionID)); + + mInputEditor->setLabel(LLTrans::getString("IM_to_label") + " " + session_name); + + setTitle(session_name); childSetCommitCallback("chat_editor", onSendMsg, this); mChatHistory = getChild<LLChatHistory>("chat_history"); - - setTitle(LLIMModel::instance().getName(mSessionID)); + setDocked(true); - - if ( gSavedPerAccountSettings.getBOOL("LogShowHistory") ) + + mTypingStart = LLTrans::getString("IM_typing_start_string"); + + // Disable input editor if session cannot accept text + LLIMModel::LLIMSession* im_session = + LLIMModel::instance().findIMSession(mSessionID); + if( im_session && !im_session->mTextIMPossible ) { - LLLogChat::loadHistory(getTitle(), &chatFromLogFile, (void *)this); + mInputEditor->setEnabled(FALSE); + mInputEditor->setLabel(LLTrans::getString("IM_unavailable_text_label")); } //*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(); + if(isChatMultiTab()) + { + return LLFloater::postBuild(); + } + else + { + 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); + } + } + + LLTransientDockableFloater::draw(); +} // static @@ -255,82 +346,140 @@ void* LLIMFloater::createPanelGroupControl(void* userdata) return self->mControlPanel; } +// static +void* LLIMFloater::createPanelAdHocControl(void* userdata) +{ + LLIMFloater *self = (LLIMFloater*)userdata; + self->mControlPanel = new LLPanelAdHocControlPanel(self->mSessionID); + self->mControlPanel->setXMLFilename("panel_adhoc_control_panel.xml"); + return self->mControlPanel; +} + void LLIMFloater::onSlide() { - LLPanel* im_control_panel = getChild<LLPanel>("panel_im_control_panel"); - im_control_panel->setVisible(!im_control_panel->getVisible()); + mControlPanel->setVisible(!mControlPanel->getVisible()); + + gSavedSettings.setBOOL("IMShowControlPanel", mControlPanel->getVisible()); + + getChild<LLButton>("slide_left_btn")->setVisible(mControlPanel->getVisible()); + getChild<LLButton>("slide_right_btn")->setVisible(!mControlPanel->getVisible()); - getChild<LLButton>("slide_left_btn")->setVisible(im_control_panel->getVisible()); - getChild<LLButton>("slide_right_btn")->setVisible(!im_control_panel->getVisible()); + LLLayoutStack* stack = getChild<LLLayoutStack>("im_panels"); + if (stack) stack->setAnimate(true); } //static LLIMFloater* LLIMFloater::show(const LLUUID& session_id) { - //hide all - LLFloaterReg::const_instance_list_t& inst_list = LLFloaterReg::getFloaterList("impanel"); - for (LLFloaterReg::const_instance_list_t::const_iterator iter = inst_list.begin(); - iter != inst_list.end(); ++iter) + if (!gIMMgr->hasSession(session_id)) return NULL; + + if(!isChatMultiTab()) { - LLIMFloater* floater = dynamic_cast<LLIMFloater*>(*iter); - if (floater && floater->isDocked()) + //hide all + LLFloaterReg::const_instance_list_t& inst_list = LLFloaterReg::getFloaterList("impanel"); + for (LLFloaterReg::const_instance_list_t::const_iterator iter = inst_list.begin(); + iter != inst_list.end(); ++iter) { - floater->setVisible(false); + LLIMFloater* floater = dynamic_cast<LLIMFloater*>(*iter); + if (floater && floater->isDocked()) + { + floater->setVisible(false); + } } } - LLIMFloater* floater = LLFloaterReg::showTypedInstance<LLIMFloater>("impanel", session_id); + bool exist = findInstance(session_id); - floater->updateMessages(); - floater->mInputEditor->setFocus(TRUE); + LLIMFloater* floater = getInstance(session_id); + if (!floater) return NULL; - if (floater->getDockControl() == NULL) + if(isChatMultiTab()) { - LLChiclet* chiclet = - LLBottomTray::getInstance()->getChicletPanel()->findChiclet<LLChiclet>( - session_id); - if (chiclet == NULL) + LLIMFloaterContainer* floater_container = LLIMFloaterContainer::getInstance(); + + // do not add existed floaters to avoid adding torn off instances + if (!exist) { - llerror("Dock chiclet for LLIMFloater doesn't exists", 0); + // LLTabContainer::eInsertionPoint i_pt = user_initiated ? LLTabContainer::RIGHT_OF_CURRENT : LLTabContainer::END; + // TODO: mantipov: use LLTabContainer::RIGHT_OF_CURRENT if it exists + LLTabContainer::eInsertionPoint i_pt = LLTabContainer::END; + + if (floater_container) + { + floater_container->addFloater(floater, TRUE, i_pt); + } } - else + + floater->openFloater(floater->getKey()); + } + else + { + // Docking may move chat window, hide it before moving, or user will see how window "jumps" + floater->setVisible(false); + + if (floater->getDockControl() == NULL) { - LLBottomTray::getInstance()->getChicletPanel()->scrollToChiclet(chiclet); + LLChiclet* chiclet = + LLBottomTray::getInstance()->getChicletPanel()->findChiclet<LLChiclet>( + session_id); + if (chiclet == NULL) + { + llerror("Dock chiclet for LLIMFloater doesn't exists", 0); + } + else + { + LLBottomTray::getInstance()->getChicletPanel()->scrollToChiclet(chiclet); + } + + floater->setDockControl(new LLDockControl(chiclet, floater, floater->getDockTongue(), + LLDockControl::TOP, boost::bind(&LLIMFloater::getAllowedRect, floater, _1))); } - floater->setDockControl(new LLDockControl(chiclet, floater, floater->getDockTongue(), - LLDockControl::TOP, boost::bind(&LLIMFloater::getAllowedRect, floater, _1))); + // window is positioned, now we can show it. } + floater->setVisible(TRUE); return floater; } void LLIMFloater::getAllowedRect(LLRect& rect) { - rect = gViewerWindow->getWorldViewRect(); + rect = gViewerWindow->getWorldViewRectScaled(); + static S32 right_padding = 0; + if (right_padding == 0) + { + LLPanel* side_bar_tabs = + gViewerWindow->getRootView()->getChild<LLPanel> ( + "side_bar_tabs"); + right_padding = side_bar_tabs->getRect().getWidth(); + LLTransientFloaterMgr::getInstance()->addControlView(side_bar_tabs); + } + rect.mRight -= right_padding; } void LLIMFloater::setDocked(bool docked, bool pop_on_undock) { // update notification channel state - LLNotificationsUI::LLScreenChannel* channel = dynamic_cast<LLNotificationsUI::LLScreenChannel*> + LLNotificationsUI::LLScreenChannel* channel = static_cast<LLNotificationsUI::LLScreenChannel*> (LLNotificationsUI::LLChannelManager::getInstance()-> findChannelByID(LLUUID(gSavedSettings.getString("NotificationChannelUUID")))); - - setCanResize(!docked); - LLTransientDockableFloater::setDocked(docked, pop_on_undock); + if(!isChatMultiTab()) + { + LLTransientDockableFloater::setDocked(docked, pop_on_undock); + } // update notification channel state if(channel) { channel->updateShowToastsState(); + channel->redrawToasts(); } } void LLIMFloater::setVisible(BOOL visible) { - LLNotificationsUI::LLScreenChannel* channel = dynamic_cast<LLNotificationsUI::LLScreenChannel*> + LLNotificationsUI::LLScreenChannel* channel = static_cast<LLNotificationsUI::LLScreenChannel*> (LLNotificationsUI::LLChannelManager::getInstance()-> findChannelByID(LLUUID(gSavedSettings.getString("NotificationChannelUUID")))); LLTransientDockableFloater::setVisible(visible); @@ -339,34 +488,82 @@ void LLIMFloater::setVisible(BOOL visible) if(channel) { channel->updateShowToastsState(); + channel->redrawToasts(); + } + + BOOL is_minimized = visible && isChatMultiTab() + ? LLIMFloaterContainer::getInstance()->isMinimized() + : !visible; + + if (!is_minimized && mChatHistory && mInputEditor) + { + //only if floater was construced and initialized from xml + updateMessages(); + //prevent steal focus when IM opened in multitab mode + if (!isChatMultiTab()) + { + mInputEditor->setFocus(TRUE); + } + } + + if(!visible) + { + LLIMChiclet* chiclet = LLBottomTray::getInstance()->getChicletPanel()->findChiclet<LLIMChiclet>(mSessionID); + if(chiclet) + { + chiclet->setToggleState(false); + } } } -//static -bool LLIMFloater::toggle(const LLUUID& session_id) +BOOL LLIMFloater::getVisible() { - LLIMFloater* floater = LLFloaterReg::findTypedInstance<LLIMFloater>("impanel", session_id); - if (floater && floater->getVisible() && floater->isDocked()) + if(isChatMultiTab()) { - // clicking on chiclet to close floater just hides it to maintain existing - // scroll/text entry state - floater->setVisible(false); - return false; + LLIMFloaterContainer* im_container = LLIMFloaterContainer::getInstance(); + + // Treat inactive floater as invisible. + bool is_active = im_container->getActiveFloater() == this; + + //torn off floater is always inactive + if (!is_active && getHost() != im_container) + { + return LLTransientDockableFloater::getVisible(); + } + + // getVisible() returns TRUE when Tabbed IM window is minimized. + return is_active && !im_container->isMinimized() && im_container->getVisible(); } - else if(floater && !floater->isDocked()) + else { - floater->setVisible(TRUE); - floater->setFocus(TRUE); - return true; + return LLTransientDockableFloater::getVisible(); } - else +} + +//static +bool LLIMFloater::toggle(const LLUUID& session_id) +{ + if(!isChatMultiTab()) { - // ensure the list of messages is updated when floater is made visible - show(session_id); - // update number of unread notifications in the SysWell - LLBottomTray::getInstance()->getSysWell()->updateUreadIMNotifications(); - return true; + LLIMFloater* floater = LLFloaterReg::findTypedInstance<LLIMFloater>("impanel", session_id); + if (floater && floater->getVisible() && floater->hasFocus()) + { + // clicking on chiclet to close floater just hides it to maintain existing + // scroll/text entry state + floater->setVisible(false); + return false; + } + else if(floater && (!floater->isDocked() || floater->getVisible() && !floater->hasFocus())) + { + floater->setVisible(TRUE); + floater->setFocus(TRUE); + return true; + } } + + // ensure the list of messages is updated when floater is made visible + show(session_id); + return true; } //static @@ -375,14 +572,27 @@ LLIMFloater* LLIMFloater::findInstance(const LLUUID& session_id) return LLFloaterReg::findTypedInstance<LLIMFloater>("impanel", session_id); } +LLIMFloater* LLIMFloater::getInstance(const LLUUID& session_id) +{ + return LLFloaterReg::getTypedInstance<LLIMFloater>("impanel", session_id); +} + 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); + } + + // updating "Call" button from group control panel here to enable it without placing into draw() (EXT-4796) + if(gAgent.isInGroup(im_session_id)) + { + mControlPanel->updateCallButton(); } //*TODO here we should remove "starting session..." warning message if we added it in postBuild() (IB) @@ -402,55 +612,123 @@ void LLIMFloater::sessionInitReplyReceived(const LLUUID& im_session_id) void LLIMFloater::updateMessages() { - std::list<LLSD> messages = LLIMModel::instance().getMessages(mSessionID, mLastMessageIndex+1); - std::string agent_name; + bool use_plain_text_chat_history = gSavedSettings.getBOOL("PlainTextChatHistory"); + + std::list<LLSD> messages; - gCacheName->getFullName(gAgentID, agent_name); + // we shouldn't reset unread message counters if IM floater doesn't have focus + if (hasFocus()) + { + LLIMModel::instance().getMessages(mSessionID, messages, mLastMessageIndex+1); + } + else + { + LLIMModel::instance().getMessagesSilently(mSessionID, messages, mLastMessageIndex+1); + } if (messages.size()) { - LLUIColor chat_color = LLUIColorTable::instance().getColor("IMChatColor"); +// LLUIColor chat_color = LLUIColorTable::instance().getColor("IMChatColor"); + + LLSD chat_args; + chat_args["use_plain_text_chat_history"] = use_plain_text_chat_history; std::ostringstream message; std::list<LLSD>::const_reverse_iterator iter = messages.rbegin(); std::list<LLSD>::const_reverse_iterator iter_end = messages.rend(); - for (; iter != iter_end; ++iter) + for (; iter != iter_end; ++iter) { LLSD msg = *iter; - std::string from = msg["from"].asString(); std::string time = msg["time"].asString(); LLUUID from_id = msg["from_id"].asUUID(); + std::string from = msg["from"].asString(); std::string message = msg["message"].asString(); - LLStyle::Params style_params; - style_params.color(chat_color); + bool is_history = msg["is_history"].asBoolean(); - if (from == agent_name) - from = LLTrans::getString("You"); - - mChatHistory->appendWidgetMessage(from_id, from, time, message, style_params); + LLChat chat; + chat.mFromID = from_id; + chat.mSessionID = mSessionID; + chat.mFromName = from; + chat.mTimeStr = time; + chat.mChatStyle = is_history ? CHAT_STYLE_HISTORY : chat.mChatStyle; + // process offer notification + if (msg.has("notification_id")) + { + chat.mNotifId = msg["notification_id"].asUUID(); + // if notification exists - embed it + if (LLNotificationsUtil::find(chat.mNotifId) != NULL) + { + // remove embedded notification from channel + LLNotificationsUI::LLScreenChannel* channel = static_cast<LLNotificationsUI::LLScreenChannel*> + (LLNotificationsUI::LLChannelManager::getInstance()-> + findChannelByID(LLUUID(gSavedSettings.getString("NotificationChannelUUID")))); + if (getVisible()) + { + // toast will be automatically closed since it is not storable toast + channel->hideToast(chat.mNotifId); + } + } + // if notification doesn't exist - try to use next message which should be log entry + else + { + continue; + } + } + //process text message + else + { + chat.mText = message; + } + + mChatHistory->appendMessage(chat, chat_args); mLastMessageIndex = msg["index"].asInteger(); + + // if it is a notification - next message is a notification history log, so skip it + if (chat.mNotifId.notNull() && LLNotificationsUtil::find(chat.mNotifId) != NULL) + { + if (++iter == iter_end) + { + break; + } + else + { + mLastMessageIndex++; + } + } } } } +void LLIMFloater::reloadMessages() +{ + mChatHistory->clear(); + mLastMessageIndex = -1; + updateMessages(); +} + // static void LLIMFloater::onInputEditorFocusReceived( LLFocusableElement* caller, void* userdata ) { LLIMFloater* self= (LLIMFloater*) userdata; - //in disconnected state IM input editor should be disabled - self->mInputEditor->setEnabled(!gDisconnected); - - self->mChatHistory->setCursorAndScrollToEnd(); + // Allow enabling the LLIMFloater input editor only if session can accept text + LLIMModel::LLIMSession* im_session = + LLIMModel::instance().findIMSession(self->mSessionID); + //TODO: While disabled lllineeditor can receive focus we need to check if it is enabled (EK) + if( im_session && im_session->mTextIMPossible && self->mInputEditor->getEnabled()) + { + //in disconnected state IM input editor should be disabled + self->mInputEditor->setEnabled(!gDisconnected); + } } // static void LLIMFloater::onInputEditorFocusLost(LLFocusableElement* caller, void* userdata) { LLIMFloater* self = (LLIMFloater*) userdata; - self->setTyping(FALSE); + self->setTyping(false); } // static @@ -460,53 +738,397 @@ 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::processAgentListUpdates(const LLSD& body) +{ + if ( !body.isMap() ) return; + + if ( body.has("agent_updates") && body["agent_updates"].isMap() ) + { + LLSD agent_data = body["agent_updates"].get(gAgentID.asString()); + if (agent_data.isMap() && agent_data.has("info")) + { + LLSD agent_info = agent_data["info"]; + + if (agent_info.has("mutes")) + { + BOOL moderator_muted_text = agent_info["mutes"]["text"].asBoolean(); + mInputEditor->setEnabled(!moderator_muted_text); + std::string label; + if (moderator_muted_text) + label = LLTrans::getString("IM_muted_text_label"); + else + label = LLTrans::getString("IM_to_label") + " " + LLIMModel::instance().getName(mSessionID); + mInputEditor->setLabel(label); + + if (moderator_muted_text) + LLNotificationsUtil::add("TextChatIsMutedByModerator"); + } + } + } +} -//just a stub for now -void LLIMFloater::setTyping(BOOL typing) +void LLIMFloater::updateChatHistoryStyle() { + mChatHistory->clear(); + mLastMessageIndex = -1; + updateMessages(); } -void LLIMFloater::chatFromLogFile(LLLogChat::ELogLineType type, std::string line, void* userdata) +void LLIMFloater::processChatHistoryStyleUpdate(const LLSD& newvalue) { - if (!userdata) return; + LLFloaterReg::const_instance_list_t& inst_list = LLFloaterReg::getFloaterList("impanel"); + for (LLFloaterReg::const_instance_list_t::const_iterator iter = inst_list.begin(); + iter != inst_list.end(); ++iter) + { + LLIMFloater* floater = dynamic_cast<LLIMFloater*>(*iter); + if (floater) + { + floater->updateChatHistoryStyle(); + } + } - LLIMFloater* self = (LLIMFloater*) userdata; - std::string message = line; - S32 im_log_option = gSavedPerAccountSettings.getS32("IMLogOptions"); - switch (type) +} + +void LLIMFloater::processSessionUpdate(const LLSD& session_update) +{ + // *TODO : verify following code when moderated mode will be implemented + if ( false && session_update.has("moderated_mode") && + session_update["moderated_mode"].has("voice") ) { - case LLLogChat::LOG_EMPTY: - // add warning log enabled message - if (im_log_option!=LOG_CHAT) + BOOL voice_moderated = session_update["moderated_mode"]["voice"]; + const std::string session_label = LLIMModel::instance().getName(mSessionID); + + if (voice_moderated) + { + setTitle(session_label + std::string(" ") + LLTrans::getString("IM_moderated_chat_label")); + } + else + { + setTitle(session_label); + } + + // *TODO : uncomment this when/if LLPanelActiveSpeakers panel will be added + //update the speakers dropdown too + //mSpeakerPanel->setVoiceModerationCtrlMode(voice_moderated); + } +} + +BOOL LLIMFloater::handleDragAndDrop(S32 x, S32 y, MASK mask, + BOOL drop, EDragAndDropType cargo_type, + void *cargo_data, EAcceptance *accept, + std::string& tooltip_msg) +{ + + if (mDialog == IM_NOTHING_SPECIAL) + { + LLToolDragAndDrop::handleGiveDragAndDrop(mOtherParticipantUUID, mSessionID, drop, + cargo_type, cargo_data, accept); + } + + // handle case for dropping calling cards (and folders of calling cards) onto invitation panel for invites + else if (isInviteAllowed()) + { + *accept = ACCEPT_NO; + + if (cargo_type == DAD_CALLINGCARD) { - message = LLTrans::getString("IM_logging_string"); + if (dropCallingCard((LLInventoryItem*)cargo_data, drop)) + { + *accept = ACCEPT_YES_MULTI; + } } - break; - case LLLogChat::LOG_END: - // add log end message - if (im_log_option!=LOG_CHAT) + else if (cargo_type == DAD_CATEGORY) { - message = LLTrans::getString("IM_logging_string"); + if (dropCategory((LLInventoryCategory*)cargo_data, drop)) + { + *accept = ACCEPT_YES_MULTI; + } } - break; - case LLLogChat::LOG_LINE: - // just add normal lines from file - break; - default: - // nothing - break; } + return TRUE; +} - self->mChatHistory->appendText(message, true, LLStyle::Params().color(LLUIColorTable::instance().getColor("ChatHistoryTextColor"))); - self->mChatHistory->blockUndo(); +BOOL LLIMFloater::dropCallingCard(LLInventoryItem* item, BOOL drop) +{ + BOOL rv = isInviteAllowed(); + if(rv && item && item->getCreatorUUID().notNull()) + { + if(drop) + { + uuid_vec_t ids; + ids.push_back(item->getCreatorUUID()); + inviteToSession(ids); + } + } + else + { + // set to false if creator uuid is null. + rv = FALSE; + } + return rv; } +BOOL LLIMFloater::dropCategory(LLInventoryCategory* category, BOOL drop) +{ + BOOL rv = isInviteAllowed(); + if(rv && category) + { + LLInventoryModel::cat_array_t cats; + LLInventoryModel::item_array_t items; + LLUniqueBuddyCollector buddies; + gInventory.collectDescendentsIf(category->getUUID(), + cats, + items, + LLInventoryModel::EXCLUDE_TRASH, + buddies); + S32 count = items.count(); + if(count == 0) + { + rv = FALSE; + } + else if(drop) + { + uuid_vec_t ids; + ids.reserve(count); + for(S32 i = 0; i < count; ++i) + { + ids.push_back(items.get(i)->getCreatorUUID()); + } + inviteToSession(ids); + } + } + return rv; +} + +BOOL LLIMFloater::isInviteAllowed() const +{ + + return ( (IM_SESSION_CONFERENCE_START == mDialog) + || (IM_SESSION_INVITE == mDialog) ); +} + +class LLSessionInviteResponder : public LLHTTPClient::Responder +{ +public: + LLSessionInviteResponder(const LLUUID& session_id) + { + mSessionID = session_id; + } + + void error(U32 statusNum, const std::string& reason) + { + llinfos << "Error inviting all agents to session" << llendl; + //throw something back to the viewer here? + } + +private: + LLUUID mSessionID; +}; + +BOOL LLIMFloater::inviteToSession(const uuid_vec_t& ids) +{ + LLViewerRegion* region = gAgent.getRegion(); + if (!region) + { + return FALSE; + } + + S32 count = ids.size(); + + if( isInviteAllowed() && (count > 0) ) + { + llinfos << "LLIMFloater::inviteToSession() - inviting participants" << llendl; + + std::string url = region->getCapability("ChatSessionRequest"); + + LLSD data; + + data["params"] = LLSD::emptyArray(); + for (int i = 0; i < count; i++) + { + data["params"].append(ids[i]); + } + + data["method"] = "invite"; + data["session-id"] = mSessionID; + LLHTTPClient::post( + url, + data, + new LLSessionInviteResponder( + mSessionID)); + } + else + { + llinfos << "LLIMFloater::inviteToSession -" + << " no need to invite agents for " + << mDialog << llendl; + // successful add, because everyone that needed to get added + // was added. + } + + return TRUE; +} + +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; + + // 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); + } + } + + } +} + +// static +bool LLIMFloater::isChatMultiTab() +{ + // Restart is required in order to change chat window type. + static bool is_single_window = gSavedSettings.getS32("ChatWindow") == 1; + return is_single_window; +} + +// static +void LLIMFloater::initIMFloater() +{ + // This is called on viewer start up + // init chat window type before user changed it in preferences + isChatMultiTab(); +} + +//static +void LLIMFloater::sRemoveTypingIndicator(const LLSD& data) +{ + LLUUID session_id = data["session_id"]; + if (session_id.isNull()) return; + + LLUUID from_id = data["from_id"]; + if (gAgentID == from_id || LLUUID::null == from_id) return; + + LLIMFloater* floater = LLIMFloater::findInstance(session_id); + if (!floater) return; + + if (IM_NOTHING_SPECIAL != floater->mDialog) return; + + floater->removeTypingIndicator(); +} + +void LLIMFloater::onIMChicletCreated( const LLUUID& session_id ) +{ + + if (isChatMultiTab()) + { + LLIMFloaterContainer* im_box = LLIMFloaterContainer::getInstance(); + if (!im_box) return; + + if (LLIMFloater::findInstance(session_id)) return; + + LLIMFloater* new_tab = LLIMFloater::getInstance(session_id); + + im_box->addFloater(new_tab, FALSE, LLTabContainer::END); + } + +} |