PageRenderTime 94ms CodeModel.GetById 17ms app.highlight 68ms RepoModel.GetById 1ms app.codeStats 1ms

/indra/newview/llimfloater.cpp

https://bitbucket.org/lindenlab/viewer-beta/
C++ | 1193 lines | 898 code | 175 blank | 120 comment | 176 complexity | 08da5b8084c23d439ef6b7195b5c7be5 MD5 | raw file
   1/** 
   2 * @file llimfloater.cpp
   3 * @brief LLIMFloater class definition
   4 *
   5 * $LicenseInfo:firstyear=2009&license=viewerlgpl$
   6 * Second Life Viewer Source Code
   7 * Copyright (C) 2010, Linden Research, Inc.
   8 * 
   9 * This library is free software; you can redistribute it and/or
  10 * modify it under the terms of the GNU Lesser General Public
  11 * License as published by the Free Software Foundation;
  12 * version 2.1 of the License only.
  13 * 
  14 * This library is distributed in the hope that it will be useful,
  15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
  16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  17 * Lesser General Public License for more details.
  18 * 
  19 * You should have received a copy of the GNU Lesser General Public
  20 * License along with this library; if not, write to the Free Software
  21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
  22 * 
  23 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
  24 * $/LicenseInfo$
  25 */
  26
  27#include "llviewerprecompiledheaders.h"
  28
  29#include "llimfloater.h"
  30
  31#include "llnotificationsutil.h"
  32
  33#include "llagent.h"
  34#include "llappviewer.h"
  35#include "llavatarnamecache.h"
  36#include "llbutton.h"
  37#include "llchannelmanager.h"
  38#include "llchiclet.h"
  39#include "llchicletbar.h"
  40#include "llfloaterreg.h"
  41#include "llimfloatercontainer.h" // to replace separate IM Floaters with multifloater container
  42#include "llinventoryfunctions.h"
  43#include "lllayoutstack.h"
  44#include "lllineeditor.h"
  45#include "lllogchat.h"
  46#include "llpanelimcontrolpanel.h"
  47#include "llscreenchannel.h"
  48#include "llsyswellwindow.h"
  49#include "lltrans.h"
  50#include "llchathistory.h"
  51#include "llnotifications.h"
  52#include "llviewerwindow.h"
  53#include "llvoicechannel.h"
  54#include "lltransientfloatermgr.h"
  55#include "llinventorymodel.h"
  56#include "llrootview.h"
  57#include "llspeakers.h"
  58#include "llviewerchat.h"
  59
  60
  61LLIMFloater::LLIMFloater(const LLUUID& session_id)
  62  : LLTransientDockableFloater(NULL, true, session_id),
  63	mControlPanel(NULL),
  64	mSessionID(session_id),
  65	mLastMessageIndex(-1),
  66	mDialog(IM_NOTHING_SPECIAL),
  67	mChatHistory(NULL),
  68	mInputEditor(NULL),
  69	mSavedTitle(),
  70	mTypingStart(),
  71	mShouldSendTypingState(false),
  72	mMeTyping(false),
  73	mOtherTyping(false),
  74	mTypingTimer(),
  75	mTypingTimeoutTimer(),
  76	mPositioned(false),
  77	mSessionInitialized(false)
  78{
  79	LLIMModel::LLIMSession* im_session = LLIMModel::getInstance()->findIMSession(mSessionID);
  80	if (im_session)
  81	{
  82		mSessionInitialized = im_session->mSessionInitialized;
  83		
  84		mDialog = im_session->mType;
  85		switch(mDialog){
  86		case IM_NOTHING_SPECIAL:
  87		case IM_SESSION_P2P_INVITE:
  88			mFactoryMap["panel_im_control_panel"] = LLCallbackMap(createPanelIMControl, this);
  89			break;
  90		case IM_SESSION_CONFERENCE_START:
  91			mFactoryMap["panel_im_control_panel"] = LLCallbackMap(createPanelAdHocControl, this);
  92			break;
  93		case IM_SESSION_GROUP_START:
  94			mFactoryMap["panel_im_control_panel"] = LLCallbackMap(createPanelGroupControl, this);
  95			break;
  96		case IM_SESSION_INVITE:		
  97			if (gAgent.isInGroup(mSessionID))
  98			{
  99				mFactoryMap["panel_im_control_panel"] = LLCallbackMap(createPanelGroupControl, this);
 100			}
 101			else
 102			{
 103				mFactoryMap["panel_im_control_panel"] = LLCallbackMap(createPanelAdHocControl, this);
 104			}
 105			break;
 106		default: break;
 107		}
 108	}
 109	setOverlapsScreenChannel(true);
 110
 111	LLTransientFloaterMgr::getInstance()->addControlView(LLTransientFloaterMgr::IM, this);
 112
 113	setDocked(true);
 114}
 115
 116void LLIMFloater::onFocusLost()
 117{
 118	LLIMModel::getInstance()->resetActiveSessionID();
 119	
 120	LLChicletBar::getInstance()->getChicletPanel()->setChicletToggleState(mSessionID, false);
 121}
 122
 123void LLIMFloater::onFocusReceived()
 124{
 125	LLIMModel::getInstance()->setActiveSessionID(mSessionID);
 126
 127	LLChicletBar::getInstance()->getChicletPanel()->setChicletToggleState(mSessionID, true);
 128
 129	if (getVisible())
 130	{
 131		LLIMModel::instance().sendNoUnreadMessages(mSessionID);
 132	}
 133}
 134
 135// virtual
 136void LLIMFloater::onClose(bool app_quitting)
 137{
 138	setTyping(false);
 139
 140	// The source of much argument and design thrashing
 141	// Should the window hide or the session close when the X is clicked?
 142	//
 143	// Last change:
 144	// EXT-3516 X Button should end IM session, _ button should hide
 145	gIMMgr->leaveSession(mSessionID);
 146}
 147
 148/* static */
 149void LLIMFloater::newIMCallback(const LLSD& data){
 150	
 151	if (data["num_unread"].asInteger() > 0 || data["from_id"].asUUID().isNull())
 152	{
 153		LLUUID session_id = data["session_id"].asUUID();
 154
 155		LLIMFloater* floater = LLFloaterReg::findTypedInstance<LLIMFloater>("impanel", session_id);
 156		if (floater == NULL) return;
 157
 158        // update if visible, otherwise will be updated when opened
 159		if (floater->getVisible())
 160		{
 161			floater->updateMessages();
 162		}
 163	}
 164}
 165
 166void LLIMFloater::onVisibilityChange(const LLSD& new_visibility)
 167{
 168	bool visible = new_visibility.asBoolean();
 169
 170	LLVoiceChannel* voice_channel = LLIMModel::getInstance()->getVoiceChannel(mSessionID);
 171
 172	if (visible && voice_channel &&
 173		voice_channel->getState() == LLVoiceChannel::STATE_CONNECTED)
 174	{
 175		LLFloaterReg::showInstance("voice_call", mSessionID);
 176	}
 177	else
 178	{
 179		LLFloaterReg::hideInstance("voice_call", mSessionID);
 180	}
 181}
 182
 183void LLIMFloater::onSendMsg( LLUICtrl* ctrl, void* userdata )
 184{
 185	LLIMFloater* self = (LLIMFloater*) userdata;
 186	self->sendMsg();
 187	self->setTyping(false);
 188}
 189
 190void LLIMFloater::sendMsg()
 191{
 192	if (!gAgent.isGodlike() 
 193		&& (mDialog == IM_NOTHING_SPECIAL)
 194		&& mOtherParticipantUUID.isNull())
 195	{
 196		llinfos << "Cannot send IM to everyone unless you're a god." << llendl;
 197		return;
 198	}
 199
 200	if (mInputEditor)
 201	{
 202		LLWString text = mInputEditor->getConvertedText();
 203		if(!text.empty())
 204		{
 205			// Truncate and convert to UTF8 for transport
 206			std::string utf8_text = wstring_to_utf8str(text);
 207			utf8_text = utf8str_truncate(utf8_text, MAX_MSG_BUF_SIZE - 1);
 208			
 209			if (mSessionInitialized)
 210			{
 211				LLIMModel::sendMessage(utf8_text, mSessionID,
 212					mOtherParticipantUUID,mDialog);
 213			}
 214			else
 215			{
 216				//queue up the message to send once the session is initialized
 217				mQueuedMsgsForInit.append(utf8_text);
 218			}
 219
 220			mInputEditor->setText(LLStringUtil::null);
 221
 222			updateMessages();
 223		}
 224	}
 225}
 226
 227
 228
 229LLIMFloater::~LLIMFloater()
 230{
 231	LLTransientFloaterMgr::getInstance()->removeControlView(LLTransientFloaterMgr::IM, this);
 232}
 233
 234//virtual
 235BOOL LLIMFloater::postBuild()
 236{
 237	const LLUUID& other_party_id = LLIMModel::getInstance()->getOtherParticipantID(mSessionID);
 238	if (other_party_id.notNull())
 239	{
 240		mOtherParticipantUUID = other_party_id;
 241	}
 242
 243	mControlPanel->setSessionId(mSessionID);
 244	mControlPanel->getParent()->setVisible(gSavedSettings.getBOOL("IMShowControlPanel"));
 245
 246	LLButton* slide_left = getChild<LLButton>("slide_left_btn");
 247	slide_left->setVisible(mControlPanel->getParent()->getVisible());
 248	slide_left->setClickedCallback(boost::bind(&LLIMFloater::onSlide, this));
 249
 250	LLButton* slide_right = getChild<LLButton>("slide_right_btn");
 251	slide_right->setVisible(!mControlPanel->getParent()->getVisible());
 252	slide_right->setClickedCallback(boost::bind(&LLIMFloater::onSlide, this));
 253
 254	mInputEditor = getChild<LLLineEditor>("chat_editor");
 255	mInputEditor->setMaxTextLength(1023);
 256	// enable line history support for instant message bar
 257	mInputEditor->setEnableLineHistory(TRUE);
 258
 259	LLFontGL* font = LLViewerChat::getChatFont();
 260	mInputEditor->setFont(font);	
 261	
 262	mInputEditor->setFocusReceivedCallback( boost::bind(onInputEditorFocusReceived, _1, this) );
 263	mInputEditor->setFocusLostCallback( boost::bind(onInputEditorFocusLost, _1, this) );
 264	mInputEditor->setKeystrokeCallback( onInputEditorKeystroke, this );
 265	mInputEditor->setCommitOnFocusLost( FALSE );
 266	mInputEditor->setRevertOnEsc( FALSE );
 267	mInputEditor->setReplaceNewlinesWithSpaces( FALSE );
 268	mInputEditor->setPassDelete( TRUE );
 269
 270	childSetCommitCallback("chat_editor", onSendMsg, this);
 271	
 272	mChatHistory = getChild<LLChatHistory>("chat_history");
 273
 274	setDocked(true);
 275
 276	mTypingStart = LLTrans::getString("IM_typing_start_string");
 277
 278	// Disable input editor if session cannot accept text
 279	LLIMModel::LLIMSession* im_session =
 280		LLIMModel::instance().findIMSession(mSessionID);
 281	if( im_session && !im_session->mTextIMPossible )
 282	{
 283		mInputEditor->setEnabled(FALSE);
 284		mInputEditor->setLabel(LLTrans::getString("IM_unavailable_text_label"));
 285	}
 286
 287	if ( im_session && im_session->isP2PSessionType())
 288	{
 289		// look up display name for window title
 290		LLAvatarNameCache::get(im_session->mOtherParticipantID,
 291							   boost::bind(&LLIMFloater::onAvatarNameCache,
 292										   this, _1, _2));
 293	}
 294	else
 295	{
 296		std::string session_name(LLIMModel::instance().getName(mSessionID));
 297		updateSessionName(session_name, session_name);
 298	}
 299	
 300	//*TODO if session is not initialized yet, add some sort of a warning message like "starting session...blablabla"
 301	//see LLFloaterIMPanel for how it is done (IB)
 302
 303	if(isChatMultiTab())
 304	{
 305		return LLFloater::postBuild();
 306	}
 307	else
 308	{
 309		return LLDockableFloater::postBuild();
 310	}
 311}
 312
 313void LLIMFloater::updateSessionName(const std::string& ui_title,
 314									const std::string& ui_label)
 315{
 316	mInputEditor->setLabel(LLTrans::getString("IM_to_label") + " " + ui_label);
 317	setTitle(ui_title);	
 318}
 319
 320void LLIMFloater::onAvatarNameCache(const LLUUID& agent_id,
 321									const LLAvatarName& av_name)
 322{
 323	// Use display name only for labels, as the extended name will be in the
 324	// floater title
 325	std::string ui_title = av_name.getCompleteName();
 326	updateSessionName(ui_title, av_name.mDisplayName);
 327	mTypingStart.setArg("[NAME]", ui_title);
 328}
 329
 330// virtual
 331void LLIMFloater::draw()
 332{
 333	if ( mMeTyping )
 334	{
 335		// Time out if user hasn't typed for a while.
 336		if ( mTypingTimeoutTimer.getElapsedTimeF32() > LLAgent::TYPING_TIMEOUT_SECS )
 337		{
 338			setTyping(false);
 339		}
 340	}
 341
 342	LLTransientDockableFloater::draw();
 343}
 344
 345
 346// static
 347void* LLIMFloater::createPanelIMControl(void* userdata)
 348{
 349	LLIMFloater *self = (LLIMFloater*)userdata;
 350	self->mControlPanel = new LLPanelIMControlPanel();
 351	self->mControlPanel->setXMLFilename("panel_im_control_panel.xml");
 352	return self->mControlPanel;
 353}
 354
 355
 356// static
 357void* LLIMFloater::createPanelGroupControl(void* userdata)
 358{
 359	LLIMFloater *self = (LLIMFloater*)userdata;
 360	self->mControlPanel = new LLPanelGroupControlPanel(self->mSessionID);
 361	self->mControlPanel->setXMLFilename("panel_group_control_panel.xml");
 362	return self->mControlPanel;
 363}
 364
 365// static
 366void* LLIMFloater::createPanelAdHocControl(void* userdata)
 367{
 368	LLIMFloater *self = (LLIMFloater*)userdata;
 369	self->mControlPanel = new LLPanelAdHocControlPanel(self->mSessionID);
 370	self->mControlPanel->setXMLFilename("panel_adhoc_control_panel.xml");
 371	return self->mControlPanel;
 372}
 373
 374void LLIMFloater::onSlide()
 375{
 376	mControlPanel->getParent()->setVisible(!mControlPanel->getParent()->getVisible());
 377
 378	gSavedSettings.setBOOL("IMShowControlPanel", mControlPanel->getParent()->getVisible());
 379
 380	getChild<LLButton>("slide_left_btn")->setVisible(mControlPanel->getParent()->getVisible());
 381	getChild<LLButton>("slide_right_btn")->setVisible(!mControlPanel->getParent()->getVisible());
 382}
 383
 384//static
 385LLIMFloater* LLIMFloater::show(const LLUUID& session_id)
 386{
 387	closeHiddenIMToasts();
 388
 389	if (!gIMMgr->hasSession(session_id)) return NULL;
 390
 391	if(!isChatMultiTab())
 392	{
 393		//hide all
 394		LLFloaterReg::const_instance_list_t& inst_list = LLFloaterReg::getFloaterList("impanel");
 395		for (LLFloaterReg::const_instance_list_t::const_iterator iter = inst_list.begin();
 396			 iter != inst_list.end(); ++iter)
 397		{
 398			LLIMFloater* floater = dynamic_cast<LLIMFloater*>(*iter);
 399			if (floater && floater->isDocked())
 400			{
 401				floater->setVisible(false);
 402			}
 403		}
 404	}
 405
 406	bool exist = findInstance(session_id);
 407
 408	LLIMFloater* floater = getInstance(session_id);
 409	if (!floater) return NULL;
 410
 411	if(isChatMultiTab())
 412	{
 413		LLIMFloaterContainer* floater_container = LLIMFloaterContainer::getInstance();
 414
 415		// do not add existed floaters to avoid adding torn off instances
 416		if (!exist)
 417		{
 418			//		LLTabContainer::eInsertionPoint i_pt = user_initiated ? LLTabContainer::RIGHT_OF_CURRENT : LLTabContainer::END;
 419			// TODO: mantipov: use LLTabContainer::RIGHT_OF_CURRENT if it exists
 420			LLTabContainer::eInsertionPoint i_pt = LLTabContainer::END;
 421			
 422			if (floater_container)
 423			{
 424				floater_container->addFloater(floater, TRUE, i_pt);
 425			}
 426		}
 427
 428		floater->openFloater(floater->getKey());
 429	}
 430	else
 431	{
 432		// Docking may move chat window, hide it before moving, or user will see how window "jumps"
 433		floater->setVisible(false);
 434
 435		if (floater->getDockControl() == NULL)
 436		{
 437			LLChiclet* chiclet =
 438					LLChicletBar::getInstance()->getChicletPanel()->findChiclet<LLChiclet>(
 439							session_id);
 440			if (chiclet == NULL)
 441			{
 442				llerror("Dock chiclet for LLIMFloater doesn't exists", 0);
 443			}
 444			else
 445			{
 446				LLChicletBar::getInstance()->getChicletPanel()->scrollToChiclet(chiclet);
 447			}
 448
 449			floater->setDockControl(new LLDockControl(chiclet, floater, floater->getDockTongue(),
 450					LLDockControl::BOTTOM));
 451		}
 452
 453		// window is positioned, now we can show it.
 454	}
 455	floater->setVisible(TRUE);
 456
 457	return floater;
 458}
 459
 460void LLIMFloater::setDocked(bool docked, bool pop_on_undock)
 461{
 462	// update notification channel state
 463	LLNotificationsUI::LLScreenChannel* channel = static_cast<LLNotificationsUI::LLScreenChannel*>
 464		(LLNotificationsUI::LLChannelManager::getInstance()->
 465											findChannelByID(LLUUID(gSavedSettings.getString("NotificationChannelUUID"))));
 466	
 467	if(!isChatMultiTab())
 468	{
 469		LLTransientDockableFloater::setDocked(docked, pop_on_undock);
 470	}
 471
 472	// update notification channel state
 473	if(channel)
 474	{
 475		channel->updateShowToastsState();
 476		channel->redrawToasts();
 477	}
 478}
 479
 480void LLIMFloater::setVisible(BOOL visible)
 481{
 482	LLNotificationsUI::LLScreenChannel* channel = static_cast<LLNotificationsUI::LLScreenChannel*>
 483		(LLNotificationsUI::LLChannelManager::getInstance()->
 484											findChannelByID(LLUUID(gSavedSettings.getString("NotificationChannelUUID"))));
 485	LLTransientDockableFloater::setVisible(visible);
 486
 487	// update notification channel state
 488	if(channel)
 489	{
 490		channel->updateShowToastsState();
 491		channel->redrawToasts();
 492	}
 493
 494	BOOL is_minimized = visible && isChatMultiTab()
 495		? LLIMFloaterContainer::getInstance()->isMinimized()
 496		: !visible;
 497
 498	if (!is_minimized && mChatHistory && mInputEditor)
 499	{
 500		//only if floater was construced and initialized from xml
 501		updateMessages();
 502		//prevent stealing focus when opening a background IM tab (EXT-5387, checking focus for EXT-6781)
 503		if (!isChatMultiTab() || hasFocus())
 504		{
 505			mInputEditor->setFocus(TRUE);
 506		}
 507	}
 508
 509	if(!visible)
 510	{
 511		LLIMChiclet* chiclet = LLChicletBar::getInstance()->getChicletPanel()->findChiclet<LLIMChiclet>(mSessionID);
 512		if(chiclet)
 513		{
 514			chiclet->setToggleState(false);
 515		}
 516	}
 517}
 518
 519BOOL LLIMFloater::getVisible()
 520{
 521	if(isChatMultiTab())
 522	{
 523		LLIMFloaterContainer* im_container = LLIMFloaterContainer::getInstance();
 524		
 525		// Treat inactive floater as invisible.
 526		bool is_active = im_container->getActiveFloater() == this;
 527	
 528		//torn off floater is always inactive
 529		if (!is_active && getHost() != im_container)
 530		{
 531			return LLTransientDockableFloater::getVisible();
 532		}
 533
 534		// getVisible() returns TRUE when Tabbed IM window is minimized.
 535		return is_active && !im_container->isMinimized() && im_container->getVisible();
 536	}
 537	else
 538	{
 539		return LLTransientDockableFloater::getVisible();
 540	}
 541}
 542
 543//static
 544bool LLIMFloater::toggle(const LLUUID& session_id)
 545{
 546	if(!isChatMultiTab())
 547	{
 548		LLIMFloater* floater = LLFloaterReg::findTypedInstance<LLIMFloater>("impanel", session_id);
 549		if (floater && floater->getVisible() && floater->hasFocus())
 550		{
 551			// clicking on chiclet to close floater just hides it to maintain existing
 552			// scroll/text entry state
 553			floater->setVisible(false);
 554			return false;
 555		}
 556		else if(floater && (!floater->isDocked() || floater->getVisible() && !floater->hasFocus()))
 557		{
 558			floater->setVisible(TRUE);
 559			floater->setFocus(TRUE);
 560			return true;
 561		}
 562	}
 563
 564	// ensure the list of messages is updated when floater is made visible
 565	show(session_id);
 566	return true;
 567}
 568
 569//static
 570LLIMFloater* LLIMFloater::findInstance(const LLUUID& session_id)
 571{
 572	return LLFloaterReg::findTypedInstance<LLIMFloater>("impanel", session_id);
 573}
 574
 575LLIMFloater* LLIMFloater::getInstance(const LLUUID& session_id)
 576{
 577	return LLFloaterReg::getTypedInstance<LLIMFloater>("impanel", session_id);
 578}
 579
 580void LLIMFloater::sessionInitReplyReceived(const LLUUID& im_session_id)
 581{
 582	mSessionInitialized = true;
 583
 584	//will be different only for an ad-hoc im session
 585	if (mSessionID != im_session_id)
 586	{
 587		mSessionID = im_session_id;
 588		setKey(im_session_id);
 589		mControlPanel->setSessionId(im_session_id);
 590	}
 591
 592	// updating "Call" button from group control panel here to enable it without placing into draw() (EXT-4796)
 593	if(gAgent.isInGroup(im_session_id))
 594	{
 595		mControlPanel->updateCallButton();
 596	}
 597	
 598	//*TODO here we should remove "starting session..." warning message if we added it in postBuild() (IB)
 599
 600
 601	//need to send delayed messaged collected while waiting for session initialization
 602	if (!mQueuedMsgsForInit.size()) return;
 603	LLSD::array_iterator iter;
 604	for ( iter = mQueuedMsgsForInit.beginArray();
 605		iter != mQueuedMsgsForInit.endArray();
 606		++iter)
 607	{
 608		LLIMModel::sendMessage(iter->asString(), mSessionID,
 609			mOtherParticipantUUID, mDialog);
 610	}
 611}
 612
 613void LLIMFloater::updateMessages()
 614{
 615	bool use_plain_text_chat_history = gSavedSettings.getBOOL("PlainTextChatHistory");
 616
 617	std::list<LLSD> messages;
 618
 619	// we shouldn't reset unread message counters if IM floater doesn't have focus
 620	if (hasFocus())
 621	{
 622		LLIMModel::instance().getMessages(mSessionID, messages, mLastMessageIndex+1);
 623	}
 624	else
 625	{
 626		LLIMModel::instance().getMessagesSilently(mSessionID, messages, mLastMessageIndex+1);
 627	}
 628
 629	if (messages.size())
 630	{
 631		LLSD chat_args;
 632		chat_args["use_plain_text_chat_history"] = use_plain_text_chat_history;
 633
 634		std::ostringstream message;
 635		std::list<LLSD>::const_reverse_iterator iter = messages.rbegin();
 636		std::list<LLSD>::const_reverse_iterator iter_end = messages.rend();
 637		for (; iter != iter_end; ++iter)
 638		{
 639			LLSD msg = *iter;
 640
 641			std::string time = msg["time"].asString();
 642			LLUUID from_id = msg["from_id"].asUUID();
 643			std::string from = msg["from"].asString();
 644			std::string message = msg["message"].asString();
 645			bool is_history = msg["is_history"].asBoolean();
 646
 647			LLChat chat;
 648			chat.mFromID = from_id;
 649			chat.mSessionID = mSessionID;
 650			chat.mFromName = from;
 651			chat.mTimeStr = time;
 652			chat.mChatStyle = is_history ? CHAT_STYLE_HISTORY : chat.mChatStyle;
 653
 654			// process offer notification
 655			if (msg.has("notification_id"))
 656			{
 657				chat.mNotifId = msg["notification_id"].asUUID();
 658				// if notification exists - embed it
 659				if (LLNotificationsUtil::find(chat.mNotifId) != NULL)
 660				{
 661					// remove embedded notification from channel
 662					LLNotificationsUI::LLScreenChannel* channel = static_cast<LLNotificationsUI::LLScreenChannel*>
 663							(LLNotificationsUI::LLChannelManager::getInstance()->
 664																findChannelByID(LLUUID(gSavedSettings.getString("NotificationChannelUUID"))));
 665					if (getVisible())
 666					{
 667						// toast will be automatically closed since it is not storable toast
 668						channel->hideToast(chat.mNotifId);
 669					}
 670				}
 671				// if notification doesn't exist - try to use next message which should be log entry
 672				else
 673				{
 674					continue;
 675				}
 676			}
 677			//process text message
 678			else
 679			{
 680				chat.mText = message;
 681			}
 682			
 683			mChatHistory->appendMessage(chat, chat_args);
 684			mLastMessageIndex = msg["index"].asInteger();
 685
 686			// if it is a notification - next message is a notification history log, so skip it
 687			if (chat.mNotifId.notNull() && LLNotificationsUtil::find(chat.mNotifId) != NULL)
 688			{
 689				if (++iter == iter_end)
 690				{
 691					break;
 692				}
 693				else
 694				{
 695					mLastMessageIndex++;
 696				}
 697			}
 698		}
 699	}
 700}
 701
 702void LLIMFloater::reloadMessages()
 703{
 704	mChatHistory->clear();
 705	mLastMessageIndex = -1;
 706	updateMessages();
 707}
 708
 709// static
 710void LLIMFloater::onInputEditorFocusReceived( LLFocusableElement* caller, void* userdata )
 711{
 712	LLIMFloater* self= (LLIMFloater*) userdata;
 713
 714	// Allow enabling the LLIMFloater input editor only if session can accept text
 715	LLIMModel::LLIMSession* im_session =
 716		LLIMModel::instance().findIMSession(self->mSessionID);
 717	//TODO: While disabled lllineeditor can receive focus we need to check if it is enabled (EK)
 718	if( im_session && im_session->mTextIMPossible && self->mInputEditor->getEnabled())
 719	{
 720		//in disconnected state IM input editor should be disabled
 721		self->mInputEditor->setEnabled(!gDisconnected);
 722	}
 723}
 724
 725// static
 726void LLIMFloater::onInputEditorFocusLost(LLFocusableElement* caller, void* userdata)
 727{
 728	LLIMFloater* self = (LLIMFloater*) userdata;
 729	self->setTyping(false);
 730}
 731
 732// static
 733void LLIMFloater::onInputEditorKeystroke(LLLineEditor* caller, void* userdata)
 734{
 735	LLIMFloater* self = (LLIMFloater*)userdata;
 736	std::string text = self->mInputEditor->getText();
 737	if (!text.empty())
 738	{
 739		self->setTyping(true);
 740	}
 741	else
 742	{
 743		// Deleting all text counts as stopping typing.
 744		self->setTyping(false);
 745	}
 746}
 747
 748void LLIMFloater::setTyping(bool typing)
 749{
 750	if ( typing )
 751	{
 752		// Started or proceeded typing, reset the typing timeout timer
 753		mTypingTimeoutTimer.reset();
 754	}
 755
 756	if ( mMeTyping != typing )
 757	{
 758		// Typing state is changed
 759		mMeTyping = typing;
 760		// So, should send current state
 761		mShouldSendTypingState = true;
 762		// In case typing is started, send state after some delay
 763		mTypingTimer.reset();
 764	}
 765
 766	// Don't want to send typing indicators to multiple people, potentially too
 767	// much network traffic. Only send in person-to-person IMs.
 768	if ( mShouldSendTypingState && mDialog == IM_NOTHING_SPECIAL )
 769	{
 770		if ( mMeTyping )
 771		{
 772			if ( mTypingTimer.getElapsedTimeF32() > 1.f )
 773			{
 774				// Still typing, send 'start typing' notification
 775				LLIMModel::instance().sendTypingState(mSessionID, mOtherParticipantUUID, TRUE);
 776				mShouldSendTypingState = false;
 777			}
 778		}
 779		else
 780		{
 781			// Send 'stop typing' notification immediately
 782			LLIMModel::instance().sendTypingState(mSessionID, mOtherParticipantUUID, FALSE);
 783			mShouldSendTypingState = false;
 784		}
 785	}
 786
 787	LLIMSpeakerMgr* speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(mSessionID);
 788	if (speaker_mgr)
 789		speaker_mgr->setSpeakerTyping(gAgent.getID(), FALSE);
 790
 791}
 792
 793void LLIMFloater::processIMTyping(const LLIMInfo* im_info, BOOL typing)
 794{
 795	if ( typing )
 796	{
 797		// other user started typing
 798		addTypingIndicator(im_info);
 799	}
 800	else
 801	{
 802		// other user stopped typing
 803		removeTypingIndicator(im_info);
 804	}
 805}
 806
 807void LLIMFloater::processAgentListUpdates(const LLSD& body)
 808{
 809	if ( !body.isMap() ) return;
 810
 811	if ( body.has("agent_updates") && body["agent_updates"].isMap() )
 812	{
 813		LLSD agent_data = body["agent_updates"].get(gAgentID.asString());
 814		if (agent_data.isMap() && agent_data.has("info"))
 815		{
 816			LLSD agent_info = agent_data["info"];
 817
 818			if (agent_info.has("mutes"))
 819			{
 820				BOOL moderator_muted_text = agent_info["mutes"]["text"].asBoolean(); 
 821				mInputEditor->setEnabled(!moderator_muted_text);
 822				std::string label;
 823				if (moderator_muted_text)
 824					label = LLTrans::getString("IM_muted_text_label");
 825				else
 826					label = LLTrans::getString("IM_to_label") + " " + LLIMModel::instance().getName(mSessionID);
 827				mInputEditor->setLabel(label);
 828
 829				if (moderator_muted_text)
 830					LLNotificationsUtil::add("TextChatIsMutedByModerator");
 831			}
 832		}
 833	}
 834}
 835
 836void LLIMFloater::updateChatHistoryStyle()
 837{
 838	mChatHistory->clear();
 839	mLastMessageIndex = -1;
 840	updateMessages();
 841}
 842
 843void LLIMFloater::processChatHistoryStyleUpdate(const LLSD& newvalue)
 844{
 845	LLFontGL* font = LLViewerChat::getChatFont();
 846	LLFloaterReg::const_instance_list_t& inst_list = LLFloaterReg::getFloaterList("impanel");
 847	for (LLFloaterReg::const_instance_list_t::const_iterator iter = inst_list.begin();
 848		 iter != inst_list.end(); ++iter)
 849	{
 850		LLIMFloater* floater = dynamic_cast<LLIMFloater*>(*iter);
 851		if (floater)
 852		{
 853			floater->updateChatHistoryStyle();
 854			floater->mInputEditor->setFont(font);
 855		}
 856	}
 857
 858}
 859
 860void LLIMFloater::processSessionUpdate(const LLSD& session_update)
 861{
 862	// *TODO : verify following code when moderated mode will be implemented
 863	if ( false && session_update.has("moderated_mode") &&
 864		 session_update["moderated_mode"].has("voice") )
 865	{
 866		BOOL voice_moderated = session_update["moderated_mode"]["voice"];
 867		const std::string session_label = LLIMModel::instance().getName(mSessionID);
 868
 869		if (voice_moderated)
 870		{
 871			setTitle(session_label + std::string(" ") + LLTrans::getString("IM_moderated_chat_label"));
 872		}
 873		else
 874		{
 875			setTitle(session_label);
 876		}
 877
 878		// *TODO : uncomment this when/if LLPanelActiveSpeakers panel will be added
 879		//update the speakers dropdown too
 880		//mSpeakerPanel->setVoiceModerationCtrlMode(voice_moderated);
 881	}
 882}
 883
 884BOOL LLIMFloater::handleDragAndDrop(S32 x, S32 y, MASK mask,
 885						   BOOL drop, EDragAndDropType cargo_type,
 886						   void *cargo_data, EAcceptance *accept,
 887						   std::string& tooltip_msg)
 888{
 889
 890	if (mDialog == IM_NOTHING_SPECIAL)
 891	{
 892		LLToolDragAndDrop::handleGiveDragAndDrop(mOtherParticipantUUID, mSessionID, drop,
 893												 cargo_type, cargo_data, accept);
 894	}
 895
 896	// handle case for dropping calling cards (and folders of calling cards) onto invitation panel for invites
 897	else if (isInviteAllowed())
 898	{
 899		*accept = ACCEPT_NO;
 900
 901		if (cargo_type == DAD_CALLINGCARD)
 902		{
 903			if (dropCallingCard((LLInventoryItem*)cargo_data, drop))
 904			{
 905				*accept = ACCEPT_YES_MULTI;
 906			}
 907		}
 908		else if (cargo_type == DAD_CATEGORY)
 909		{
 910			if (dropCategory((LLInventoryCategory*)cargo_data, drop))
 911			{
 912				*accept = ACCEPT_YES_MULTI;
 913			}
 914		}
 915	}
 916	return TRUE;
 917}
 918
 919BOOL LLIMFloater::dropCallingCard(LLInventoryItem* item, BOOL drop)
 920{
 921	BOOL rv = isInviteAllowed();
 922	if(rv && item && item->getCreatorUUID().notNull())
 923	{
 924		if(drop)
 925		{
 926			uuid_vec_t ids;
 927			ids.push_back(item->getCreatorUUID());
 928			inviteToSession(ids);
 929		}
 930	}
 931	else
 932	{
 933		// set to false if creator uuid is null.
 934		rv = FALSE;
 935	}
 936	return rv;
 937}
 938
 939BOOL LLIMFloater::dropCategory(LLInventoryCategory* category, BOOL drop)
 940{
 941	BOOL rv = isInviteAllowed();
 942	if(rv && category)
 943	{
 944		LLInventoryModel::cat_array_t cats;
 945		LLInventoryModel::item_array_t items;
 946		LLUniqueBuddyCollector buddies;
 947		gInventory.collectDescendentsIf(category->getUUID(),
 948										cats,
 949										items,
 950										LLInventoryModel::EXCLUDE_TRASH,
 951										buddies);
 952		S32 count = items.count();
 953		if(count == 0)
 954		{
 955			rv = FALSE;
 956		}
 957		else if(drop)
 958		{
 959			uuid_vec_t ids;
 960			ids.reserve(count);
 961			for(S32 i = 0; i < count; ++i)
 962			{
 963				ids.push_back(items.get(i)->getCreatorUUID());
 964			}
 965			inviteToSession(ids);
 966		}
 967	}
 968	return rv;
 969}
 970
 971BOOL LLIMFloater::isInviteAllowed() const
 972{
 973
 974	return ( (IM_SESSION_CONFERENCE_START == mDialog)
 975			 || (IM_SESSION_INVITE == mDialog) );
 976}
 977
 978class LLSessionInviteResponder : public LLHTTPClient::Responder
 979{
 980public:
 981	LLSessionInviteResponder(const LLUUID& session_id)
 982	{
 983		mSessionID = session_id;
 984	}
 985
 986	void error(U32 statusNum, const std::string& reason)
 987	{
 988		llinfos << "Error inviting all agents to session" << llendl;
 989		//throw something back to the viewer here?
 990	}
 991
 992private:
 993	LLUUID mSessionID;
 994};
 995
 996BOOL LLIMFloater::inviteToSession(const uuid_vec_t& ids)
 997{
 998	LLViewerRegion* region = gAgent.getRegion();
 999	if (!region)
1000	{
1001		return FALSE;
1002	}
1003
1004	S32 count = ids.size();
1005
1006	if( isInviteAllowed() && (count > 0) )
1007	{
1008		llinfos << "LLIMFloater::inviteToSession() - inviting participants" << llendl;
1009
1010		std::string url = region->getCapability("ChatSessionRequest");
1011
1012		LLSD data;
1013
1014		data["params"] = LLSD::emptyArray();
1015		for (int i = 0; i < count; i++)
1016		{
1017			data["params"].append(ids[i]);
1018		}
1019
1020		data["method"] = "invite";
1021		data["session-id"] = mSessionID;
1022		LLHTTPClient::post(
1023			url,
1024			data,
1025			new LLSessionInviteResponder(
1026					mSessionID));
1027	}
1028	else
1029	{
1030		llinfos << "LLIMFloater::inviteToSession -"
1031				<< " no need to invite agents for "
1032				<< mDialog << llendl;
1033		// successful add, because everyone that needed to get added
1034		// was added.
1035	}
1036
1037	return TRUE;
1038}
1039
1040void LLIMFloater::addTypingIndicator(const LLIMInfo* im_info)
1041{
1042	// We may have lost a "stop-typing" packet, don't add it twice
1043	if ( im_info && !mOtherTyping )
1044	{
1045		mOtherTyping = true;
1046
1047		// Save and set new title
1048		mSavedTitle = getTitle();
1049		setTitle (mTypingStart);
1050
1051		// Update speaker
1052		LLIMSpeakerMgr* speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(mSessionID);
1053		if ( speaker_mgr )
1054		{
1055			speaker_mgr->setSpeakerTyping(im_info->mFromID, TRUE);
1056		}
1057	}
1058}
1059
1060void LLIMFloater::removeTypingIndicator(const LLIMInfo* im_info)
1061{
1062	if ( mOtherTyping )
1063	{
1064		mOtherTyping = false;
1065
1066		// Revert the title to saved one
1067		setTitle(mSavedTitle);
1068
1069		if ( im_info )
1070		{
1071			// Update speaker
1072			LLIMSpeakerMgr* speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(mSessionID);
1073			if ( speaker_mgr )
1074			{
1075				speaker_mgr->setSpeakerTyping(im_info->mFromID, FALSE);
1076			}
1077		}
1078
1079	}
1080}
1081
1082// static
1083void LLIMFloater::closeHiddenIMToasts()
1084{
1085	class IMToastMatcher: public LLNotificationsUI::LLScreenChannel::Matcher
1086	{
1087	public:
1088		bool matches(const LLNotificationPtr notification) const
1089		{
1090			// "notifytoast" type of notifications is reserved for IM notifications
1091			return "notifytoast" == notification->getType();
1092		}
1093	};
1094
1095	LLNotificationsUI::LLScreenChannel* channel = LLNotificationsUI::LLChannelManager::getNotificationScreenChannel();
1096	if (channel != NULL)
1097	{
1098		channel->closeHiddenToasts(IMToastMatcher());
1099	}
1100}
1101// static
1102void LLIMFloater::confirmLeaveCallCallback(const LLSD& notification, const LLSD& response)
1103{
1104	S32 option = LLNotificationsUtil::getSelectedOption(notification, response);
1105	const LLSD& payload = notification["payload"];
1106	LLUUID session_id = payload["session_id"];
1107
1108	LLFloater* im_floater = LLFloaterReg::findInstance("impanel", session_id);
1109	if (option == 0 && im_floater != NULL)
1110	{
1111		im_floater->closeFloater();
1112	}
1113
1114	return;
1115}
1116
1117// static
1118bool LLIMFloater::isChatMultiTab()
1119{
1120	// Restart is required in order to change chat window type.
1121	static bool is_single_window = gSavedSettings.getS32("ChatWindow") == 1;
1122	return is_single_window;
1123}
1124
1125// static
1126void LLIMFloater::initIMFloater()
1127{
1128	// This is called on viewer start up
1129	// init chat window type before user changed it in preferences
1130	isChatMultiTab();
1131}
1132
1133//static
1134void LLIMFloater::sRemoveTypingIndicator(const LLSD& data)
1135{
1136	LLUUID session_id = data["session_id"];
1137	if (session_id.isNull()) return;
1138
1139	LLUUID from_id = data["from_id"];
1140	if (gAgentID == from_id || LLUUID::null == from_id) return;
1141
1142	LLIMFloater* floater = LLIMFloater::findInstance(session_id);
1143	if (!floater) return;
1144
1145	if (IM_NOTHING_SPECIAL != floater->mDialog) return;
1146
1147	floater->removeTypingIndicator();
1148}
1149
1150void LLIMFloater::onIMChicletCreated( const LLUUID& session_id )
1151{
1152
1153	if (isChatMultiTab())
1154	{
1155		LLIMFloaterContainer* im_box = LLIMFloaterContainer::getInstance();
1156		if (!im_box) return;
1157
1158		if (LLIMFloater::findInstance(session_id)) return;
1159
1160		LLIMFloater* new_tab = LLIMFloater::getInstance(session_id);
1161
1162		im_box->addFloater(new_tab, FALSE, LLTabContainer::END);
1163	}
1164
1165}
1166
1167void	LLIMFloater::onClickCloseBtn()
1168{
1169
1170	LLIMModel::LLIMSession* session = LLIMModel::instance().findIMSession(
1171				mSessionID);
1172
1173	if (session == NULL)
1174	{
1175		llwarns << "Empty session." << llendl;
1176		return;
1177	}
1178
1179	bool is_call_with_chat = session->isGroupSessionType()
1180			|| session->isAdHocSessionType() || session->isP2PSessionType();
1181
1182	LLVoiceChannel* voice_channel = LLIMModel::getInstance()->getVoiceChannel(mSessionID);
1183
1184	if (is_call_with_chat && voice_channel != NULL && voice_channel->isActive())
1185	{
1186		LLSD payload;
1187		payload["session_id"] = mSessionID;
1188		LLNotificationsUtil::add("ConfirmLeaveCall", LLSD(), payload, confirmLeaveCallCallback);
1189		return;
1190	}
1191
1192	LLFloater::onClickCloseBtn();
1193}