PageRenderTime 106ms CodeModel.GetById 7ms app.highlight 88ms RepoModel.GetById 1ms app.codeStats 0ms

/indra/newview/llimpanel.cpp

https://bitbucket.org/lindenlab/viewer-beta/
C++ | 976 lines | 733 code | 134 blank | 109 comment | 116 complexity | bf32339e59bf32cc372cb9346cc8a5db MD5 | raw file
  1/** 
  2 * @file llimpanel.cpp
  3 * @brief LLIMPanel class definition
  4 *
  5 * $LicenseInfo:firstyear=2001&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 "llimpanel.h"
 30
 31#include "indra_constants.h"
 32#include "llfloaterreg.h"
 33#include "llfocusmgr.h"
 34#include "llfontgl.h"
 35#include "llrect.h"
 36#include "llerror.h"
 37#include "llmultifloater.h"
 38#include "llstring.h"
 39#include "message.h"
 40#include "lltextbox.h"
 41
 42#include "llagent.h"
 43#include "llbutton.h"
 44#include "llcallingcard.h"
 45#include "llchannelmanager.h"
 46#include "llchat.h"
 47#include "llchiclet.h"
 48#include "llconsole.h"
 49#include "llgroupactions.h"
 50#include "llfloater.h"
 51#include "llfloateractivespeakers.h"
 52#include "llavataractions.h"
 53#include "llinventory.h"
 54#include "llinventorymodel.h"
 55#include "llfloaterinventory.h"
 56#include "lliconctrl.h"
 57#include "llkeyboard.h"
 58#include "lllineeditor.h"
 59#include "llpanelimcontrolpanel.h"
 60#include "llrecentpeople.h"
 61#include "llresmgr.h"
 62#include "lltooldraganddrop.h"
 63#include "lltrans.h"
 64#include "lltabcontainer.h"
 65#include "llviewertexteditor.h"
 66#include "llviewermessage.h"
 67#include "llviewerstats.h"
 68#include "llviewercontrol.h"
 69#include "lluictrlfactory.h"
 70#include "llviewerwindow.h"
 71#include "llvoicechannel.h"
 72#include "lllogchat.h"
 73#include "llweb.h"
 74#include "llhttpclient.h"
 75#include "llmutelist.h"
 76#include "llstylemap.h"
 77#include "llappviewer.h"
 78
 79//
 80// Constants
 81//
 82const S32 LINE_HEIGHT = 16;
 83const S32 MIN_WIDTH = 200;
 84const S32 MIN_HEIGHT = 130;
 85
 86//
 87// Statics
 88//
 89//
 90static std::string sTitleString = "Instant Message with [NAME]";
 91static std::string sTypingStartString = "[NAME]: ...";
 92static std::string sSessionStartString = "Starting session with [NAME] please wait.";
 93
 94
 95//
 96// LLFloaterIMPanel
 97//
 98
 99LLFloaterIMPanel::LLFloaterIMPanel(const std::string& session_label,
100								   const LLUUID& session_id,
101								   const LLUUID& other_participant_id,
102								   const std::vector<LLUUID>& ids,
103								   EInstantMessage dialog)
104:	LLFloater(session_id),
105	mInputEditor(NULL),
106	mHistoryEditor(NULL),
107	mSessionUUID(session_id),
108	mSessionLabel(session_label),
109	mSessionInitialized(FALSE),
110	mSessionStartMsgPos(0),
111	mOtherParticipantUUID(other_participant_id),
112	mDialog(dialog),
113	mSessionInitialTargetIDs(ids),
114	mTyping(FALSE),
115	mOtherTyping(FALSE),
116	mTypingLineStartIndex(0),
117	mSentTypingState(TRUE),
118	mNumUnreadMessages(0),
119	mShowSpeakersOnConnect(TRUE),
120	mTextIMPossible(TRUE),
121	mProfileButtonEnabled(TRUE),
122	mCallBackEnabled(TRUE),
123	mSpeakerPanel(NULL),
124	mFirstKeystrokeTimer(),
125	mLastKeystrokeTimer()
126{
127	std::string xml_filename;
128	switch(mDialog)
129	{
130	case IM_SESSION_GROUP_START:
131		mFactoryMap["active_speakers_panel"] = LLCallbackMap(createSpeakersPanel, this);
132		xml_filename = "floater_instant_message_group.xml";
133		break;
134	case IM_SESSION_INVITE:
135		mFactoryMap["active_speakers_panel"] = LLCallbackMap(createSpeakersPanel, this);
136		if (gAgent.isInGroup(mSessionUUID))
137		{
138			xml_filename = "floater_instant_message_group.xml";
139		}
140		else // must be invite to ad hoc IM
141		{
142			xml_filename = "floater_instant_message_ad_hoc.xml";
143		}
144		break;
145	case IM_SESSION_P2P_INVITE:
146		xml_filename = "floater_instant_message.xml";
147		break;
148	case IM_SESSION_CONFERENCE_START:
149		mFactoryMap["active_speakers_panel"] = LLCallbackMap(createSpeakersPanel, this);
150		xml_filename = "floater_instant_message_ad_hoc.xml";
151		break;
152	// just received text from another user
153	case IM_NOTHING_SPECIAL:
154
155		xml_filename = "floater_instant_message.xml";
156		
157		mTextIMPossible = LLVoiceClient::getInstance()->isSessionTextIMPossible(mSessionUUID);
158		mProfileButtonEnabled = LLVoiceClient::getInstance()->isParticipantAvatar(mSessionUUID);
159		mCallBackEnabled = LLVoiceClient::getInstance()->isSessionCallBackPossible(mSessionUUID);
160		break;
161	default:
162		llwarns << "Unknown session type" << llendl;
163		xml_filename = "floater_instant_message.xml";
164		break;
165	}
166
167	LLUICtrlFactory::getInstance()->buildFloater(this, xml_filename, NULL);
168
169	setTitle(mSessionLabel);
170	mInputEditor->setMaxTextLength(DB_IM_MSG_STR_LEN);
171	// enable line history support for instant message bar
172	mInputEditor->setEnableLineHistory(TRUE);
173
174	//*TODO we probably need the same "awaiting message" thing in LLIMFloater
175	LLIMModel::LLIMSession* im_session = LLIMModel::getInstance()->findIMSession(mSessionUUID);
176	if (!im_session)
177	{
178		llerror("im session with id " + mSessionUUID.asString() + " does not exist!", 0);
179		return;
180	}
181
182	mSessionInitialized =  im_session->mSessionInitialized;
183	if (!mSessionInitialized)
184	{
185		//locally echo a little "starting session" message
186		LLUIString session_start = sSessionStartString;
187
188		session_start.setArg("[NAME]", getTitle());
189		mSessionStartMsgPos = 
190			mHistoryEditor->getWText().length();
191
192		addHistoryLine(
193			session_start,
194			LLUIColorTable::instance().getColor("SystemChatColor"),
195			false);
196	}
197}
198
199
200LLFloaterIMPanel::~LLFloaterIMPanel()
201{
202	//delete focus lost callback
203	mFocusCallbackConnection.disconnect();
204}
205
206BOOL LLFloaterIMPanel::postBuild() 
207{
208	setVisibleCallback(boost::bind(&LLFloaterIMPanel::onVisibilityChange, this, _2));
209	
210	mInputEditor = getChild<LLLineEditor>("chat_editor");
211	mInputEditor->setFocusReceivedCallback( boost::bind(onInputEditorFocusReceived, _1, this) );
212	mFocusCallbackConnection = mInputEditor->setFocusLostCallback( boost::bind(onInputEditorFocusLost, _1, this));
213	mInputEditor->setKeystrokeCallback( onInputEditorKeystroke, this );
214	mInputEditor->setCommitCallback( onCommitChat, this );
215	mInputEditor->setCommitOnFocusLost( FALSE );
216	mInputEditor->setRevertOnEsc( FALSE );
217	mInputEditor->setReplaceNewlinesWithSpaces( FALSE );
218
219	childSetAction("profile_callee_btn", onClickProfile, this);
220	childSetAction("group_info_btn", onClickGroupInfo, this);
221
222	childSetAction("start_call_btn", onClickStartCall, this);
223	childSetAction("end_call_btn", onClickEndCall, this);
224	childSetAction("send_btn", onClickSend, this);
225	childSetAction("toggle_active_speakers_btn", onClickToggleActiveSpeakers, this);
226
227	childSetAction("moderator_kick_speaker", onKickSpeaker, this);
228	//LLButton* close_btn = getChild<LLButton>("close_btn");
229	//close_btn->setClickedCallback(&LLFloaterIMPanel::onClickClose, this);
230
231	mHistoryEditor = getChild<LLViewerTextEditor>("im_history");
232
233	if ( IM_SESSION_GROUP_START == mDialog )
234	{
235		childSetEnabled("profile_btn", FALSE);
236	}
237	
238	if(!mProfileButtonEnabled)
239	{
240		childSetEnabled("profile_callee_btn", FALSE);
241	}
242
243	sTitleString = getString("title_string");
244	sTypingStartString = getString("typing_start_string");
245	sSessionStartString = getString("session_start_string");
246
247	if (mSpeakerPanel)
248	{
249		mSpeakerPanel->refreshSpeakers();
250	}
251
252	if (mDialog == IM_NOTHING_SPECIAL)
253	{
254		childSetAction("mute_btn", onClickMuteVoice, this);
255		childSetCommitCallback("speaker_volume", onVolumeChange, this);
256	}
257
258	setDefaultBtn("send_btn");
259	return TRUE;
260}
261
262void* LLFloaterIMPanel::createSpeakersPanel(void* data)
263{
264	LLFloaterIMPanel* floaterp = (LLFloaterIMPanel*)data;
265	LLIMSpeakerMgr* speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(floaterp->mSessionUUID);
266	floaterp->mSpeakerPanel = new LLPanelActiveSpeakers(speaker_mgr, TRUE);
267	return floaterp->mSpeakerPanel;
268}
269
270//static 
271void LLFloaterIMPanel::onClickMuteVoice(void* user_data)
272{
273	LLFloaterIMPanel* floaterp = (LLFloaterIMPanel*)user_data;
274	if (floaterp)
275	{
276		BOOL is_muted = LLMuteList::getInstance()->isMuted(floaterp->mOtherParticipantUUID, LLMute::flagVoiceChat);
277
278		LLMute mute(floaterp->mOtherParticipantUUID, floaterp->getTitle(), LLMute::AGENT);
279		if (!is_muted)
280		{
281			LLMuteList::getInstance()->add(mute, LLMute::flagVoiceChat);
282		}
283		else
284		{
285			LLMuteList::getInstance()->remove(mute, LLMute::flagVoiceChat);
286		}
287	}
288}
289
290//static 
291void LLFloaterIMPanel::onVolumeChange(LLUICtrl* source, void* user_data)
292{
293	LLFloaterIMPanel* floaterp = (LLFloaterIMPanel*)user_data;
294	if (floaterp)
295	{
296		LLVoiceClient::getInstance()->setUserVolume(floaterp->mOtherParticipantUUID, (F32)source->getValue().asReal());
297	}
298}
299
300
301// virtual
302void LLFloaterIMPanel::draw()
303{	
304	LLViewerRegion* region = gAgent.getRegion();
305	
306	BOOL enable_connect = (region && region->getCapability("ChatSessionRequest") != "")
307					  && mSessionInitialized
308					  && LLVoiceClient::getInstance()->voiceEnabled()
309					  && mCallBackEnabled;
310
311	// hide/show start call and end call buttons
312	LLVoiceChannel* voice_channel = LLIMModel::getInstance()->getVoiceChannel(mSessionUUID);
313	if (!voice_channel)
314		return;
315
316	childSetVisible("end_call_btn", LLVoiceClient::getInstance()->voiceEnabled() && voice_channel->getState() >= LLVoiceChannel::STATE_CALL_STARTED);
317	childSetVisible("start_call_btn", LLVoiceClient::getInstance()->voiceEnabled() && voice_channel->getState() < LLVoiceChannel::STATE_CALL_STARTED);
318	childSetEnabled("start_call_btn", enable_connect);
319	childSetEnabled("send_btn", !childGetValue("chat_editor").asString().empty());
320	
321	LLPointer<LLSpeaker> self_speaker;
322	LLIMSpeakerMgr* speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(mSessionUUID);
323	if (speaker_mgr)
324	{
325		self_speaker = speaker_mgr->findSpeaker(gAgent.getID());
326	}
327	if(!mTextIMPossible)
328	{
329		mInputEditor->setEnabled(FALSE);
330		mInputEditor->setLabel(getString("unavailable_text_label"));
331	}
332	else if (self_speaker.notNull() && self_speaker->mModeratorMutedText)
333	{
334		mInputEditor->setEnabled(FALSE);
335		mInputEditor->setLabel(getString("muted_text_label"));
336	}
337	else
338	{
339		mInputEditor->setEnabled(TRUE);
340		mInputEditor->setLabel(getString("default_text_label"));
341	}
342
343	// show speakers window when voice first connects
344	if (mShowSpeakersOnConnect && voice_channel->isActive())
345	{
346		childSetVisible("active_speakers_panel", TRUE);
347		mShowSpeakersOnConnect = FALSE;
348	}
349	childSetValue("toggle_active_speakers_btn", childIsVisible("active_speakers_panel"));
350
351	if (mTyping)
352	{
353		// Time out if user hasn't typed for a while.
354		if (mLastKeystrokeTimer.getElapsedTimeF32() > LLAgent::TYPING_TIMEOUT_SECS)
355		{
356			setTyping(FALSE);
357		}
358
359		// If we are typing, and it's been a little while, send the
360		// typing indicator
361		if (!mSentTypingState
362			&& mFirstKeystrokeTimer.getElapsedTimeF32() > 1.f)
363		{
364			sendTypingState(TRUE);
365			mSentTypingState = TRUE;
366		}
367	}
368
369	// use embedded panel if available
370	if (mSpeakerPanel)
371	{
372		if (mSpeakerPanel->getVisible())
373		{
374			mSpeakerPanel->refreshSpeakers();
375		}
376	}
377	else
378	{
379		// refresh volume and mute checkbox
380		childSetVisible("speaker_volume", LLVoiceClient::getInstance()->voiceEnabled() && voice_channel->isActive());
381		childSetValue("speaker_volume", LLVoiceClient::getInstance()->getUserVolume(mOtherParticipantUUID));
382
383		childSetValue("mute_btn", LLMuteList::getInstance()->isMuted(mOtherParticipantUUID, LLMute::flagVoiceChat));
384		childSetVisible("mute_btn", LLVoiceClient::getInstance()->voiceEnabled() && voice_channel->isActive());
385	}
386	LLFloater::draw();
387}
388
389class LLSessionInviteResponder : public LLHTTPClient::Responder
390{
391public:
392	LLSessionInviteResponder(const LLUUID& session_id)
393	{
394		mSessionID = session_id;
395	}
396
397	void error(U32 statusNum, const std::string& reason)
398	{
399		llinfos << "Error inviting all agents to session" << llendl;
400		//throw something back to the viewer here?
401	}
402
403private:
404	LLUUID mSessionID;
405};
406
407BOOL LLFloaterIMPanel::inviteToSession(const std::vector<LLUUID>& ids)
408{
409	LLViewerRegion* region = gAgent.getRegion();
410	if (!region)
411	{
412		return FALSE;
413	}
414	
415	S32 count = ids.size();
416
417	if( isInviteAllowed() && (count > 0) )
418	{
419		llinfos << "LLFloaterIMPanel::inviteToSession() - inviting participants" << llendl;
420
421		std::string url = region->getCapability("ChatSessionRequest");
422
423		LLSD data;
424
425		data["params"] = LLSD::emptyArray();
426		for (int i = 0; i < count; i++)
427		{
428			data["params"].append(ids[i]);
429		}
430
431		data["method"] = "invite";
432		data["session-id"] = mSessionUUID;
433		LLHTTPClient::post(
434			url,
435			data,
436			new LLSessionInviteResponder(
437				mSessionUUID));		
438	}
439	else
440	{
441		llinfos << "LLFloaterIMPanel::inviteToSession -"
442				<< " no need to invite agents for "
443				<< mDialog << llendl;
444		// successful add, because everyone that needed to get added
445		// was added.
446	}
447
448	return TRUE;
449}
450
451void LLFloaterIMPanel::addHistoryLine(const std::string &utf8msg, const LLColor4& color, bool log_to_file, const LLUUID& source, const std::string& name)
452{
453	// start tab flashing when receiving im for background session from user
454	if (source != LLUUID::null)
455	{
456		LLMultiFloater* hostp = getHost();
457		if( !isInVisibleChain() 
458			&& hostp 
459			&& source != gAgent.getID())
460		{
461			hostp->setFloaterFlashing(this, TRUE);
462		}
463	}
464
465	// Now we're adding the actual line of text, so erase the 
466	// "Foo is typing..." text segment, and the optional timestamp
467	// if it was present. JC
468	removeTypingIndicator(NULL);
469
470	// Actually add the line
471	std::string timestring;
472	bool prepend_newline = true;
473	if (gSavedSettings.getBOOL("IMShowTimestamps"))
474	{
475		timestring = mHistoryEditor->appendTime(prepend_newline);
476		prepend_newline = false;
477	}
478
479	std::string separator_string(": ");
480	
481	// 'name' is a sender name that we want to hotlink so that clicking on it opens a profile.
482	if (!name.empty()) // If name exists, then add it to the front of the message.
483	{
484		// Don't hotlink any messages from the system (e.g. "Second Life:"), so just add those in plain text.
485		if (name == SYSTEM_FROM)
486		{
487			mHistoryEditor->appendText(name + separator_string, prepend_newline, LLStyle::Params().color(color));
488		}
489		else
490		{
491			// Convert the name to a hotlink and add to message.
492			mHistoryEditor->appendText(name + separator_string, prepend_newline, LLStyleMap::instance().lookupAgent(source));
493		}
494		prepend_newline = false;
495	}
496	mHistoryEditor->appendText(utf8msg, prepend_newline, LLStyle::Params().color(color));
497	mHistoryEditor->blockUndo();
498
499	if (!isInVisibleChain())
500	{
501		mNumUnreadMessages++;
502	}
503}
504
505
506void LLFloaterIMPanel::setInputFocus( BOOL b )
507{
508	mInputEditor->setFocus( b );
509}
510
511
512void LLFloaterIMPanel::selectAll()
513{
514	mInputEditor->selectAll();
515}
516
517
518void LLFloaterIMPanel::selectNone()
519{
520	mInputEditor->deselect();
521}
522
523BOOL LLFloaterIMPanel::handleKeyHere( KEY key, MASK mask )
524{
525	BOOL handled = FALSE;
526	if( KEY_RETURN == key && mask == MASK_NONE)
527	{
528		sendMsg();
529		handled = TRUE;
530	}
531	else if ( KEY_ESCAPE == key )
532	{
533		handled = TRUE;
534		gFocusMgr.setKeyboardFocus(NULL);
535	}
536
537	// May need to call base class LLPanel::handleKeyHere if not handled
538	// in order to tab between buttons.  JNC 1.2.2002
539	return handled;
540}
541
542BOOL LLFloaterIMPanel::handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop,
543								  EDragAndDropType cargo_type,
544								  void* cargo_data,
545								  EAcceptance* accept,
546								  std::string& tooltip_msg)
547{
548
549	if (mDialog == IM_NOTHING_SPECIAL)
550	{
551		LLToolDragAndDrop::handleGiveDragAndDrop(mOtherParticipantUUID, mSessionUUID, drop,
552												 cargo_type, cargo_data, accept);
553	}
554	
555	// handle case for dropping calling cards (and folders of calling cards) onto invitation panel for invites
556	else if (isInviteAllowed())
557	{
558		*accept = ACCEPT_NO;
559		
560		if (cargo_type == DAD_CALLINGCARD)
561		{
562			if (dropCallingCard((LLInventoryItem*)cargo_data, drop))
563			{
564				*accept = ACCEPT_YES_MULTI;
565			}
566		}
567		else if (cargo_type == DAD_CATEGORY)
568		{
569			if (dropCategory((LLInventoryCategory*)cargo_data, drop))
570			{
571				*accept = ACCEPT_YES_MULTI;
572			}
573		}
574	}
575	return TRUE;
576} 
577
578BOOL LLFloaterIMPanel::dropCallingCard(LLInventoryItem* item, BOOL drop)
579{
580	BOOL rv = isInviteAllowed();
581	if(rv && item && item->getCreatorUUID().notNull())
582	{
583		if(drop)
584		{
585			std::vector<LLUUID> ids;
586			ids.push_back(item->getCreatorUUID());
587			inviteToSession(ids);
588		}
589	}
590	else
591	{
592		// set to false if creator uuid is null.
593		rv = FALSE;
594	}
595	return rv;
596}
597
598BOOL LLFloaterIMPanel::dropCategory(LLInventoryCategory* category, BOOL drop)
599{
600	BOOL rv = isInviteAllowed();
601	if(rv && category)
602	{
603		LLInventoryModel::cat_array_t cats;
604		LLInventoryModel::item_array_t items;
605		LLUniqueBuddyCollector buddies;
606		gInventory.collectDescendentsIf(category->getUUID(),
607										cats,
608										items,
609										LLInventoryModel::EXCLUDE_TRASH,
610										buddies);
611		S32 count = items.count();
612		if(count == 0)
613		{
614			rv = FALSE;
615		}
616		else if(drop)
617		{
618			std::vector<LLUUID> ids;
619			ids.reserve(count);
620			for(S32 i = 0; i < count; ++i)
621			{
622				ids.push_back(items.get(i)->getCreatorUUID());
623			}
624			inviteToSession(ids);
625		}
626	}
627	return rv;
628}
629
630BOOL LLFloaterIMPanel::isInviteAllowed() const
631{
632
633	return ( (IM_SESSION_CONFERENCE_START == mDialog) 
634			 || (IM_SESSION_INVITE == mDialog) );
635}
636
637
638// static
639void LLFloaterIMPanel::onTabClick(void* userdata)
640{
641	LLFloaterIMPanel* self = (LLFloaterIMPanel*) userdata;
642	self->setInputFocus(TRUE);
643}
644
645
646// static
647void LLFloaterIMPanel::onClickProfile( void* userdata )
648{
649	//  Bring up the Profile window
650	LLFloaterIMPanel* self = (LLFloaterIMPanel*) userdata;
651	
652	if (self->getOtherParticipantID().notNull())
653	{
654		LLAvatarActions::showProfile(self->getOtherParticipantID());
655	}
656}
657
658// static
659void LLFloaterIMPanel::onClickGroupInfo( void* userdata )
660{
661	//  Bring up the Profile window
662	LLFloaterIMPanel* self = (LLFloaterIMPanel*) userdata;
663
664	LLGroupActions::show(self->mSessionUUID);
665
666}
667
668// static
669void LLFloaterIMPanel::onClickClose( void* userdata )
670{
671	LLFloaterIMPanel* self = (LLFloaterIMPanel*) userdata;
672	if(self)
673	{
674		self->closeFloater();
675	}
676}
677
678// static
679void LLFloaterIMPanel::onClickStartCall(void* userdata)
680{
681	LLFloaterIMPanel* self = (LLFloaterIMPanel*) userdata;
682
683	gIMMgr->startCall(self->mSessionUUID);
684}
685
686// static
687void LLFloaterIMPanel::onClickEndCall(void* userdata)
688{
689	LLFloaterIMPanel* self = (LLFloaterIMPanel*) userdata;
690
691	gIMMgr->endCall(self->mSessionUUID);
692}
693
694// static
695void LLFloaterIMPanel::onClickSend(void* userdata)
696{
697	LLFloaterIMPanel* self = (LLFloaterIMPanel*)userdata;
698	self->sendMsg();
699}
700
701// static
702void LLFloaterIMPanel::onClickToggleActiveSpeakers(void* userdata)
703{
704	LLFloaterIMPanel* self = (LLFloaterIMPanel*)userdata;
705
706	self->childSetVisible("active_speakers_panel", !self->childIsVisible("active_speakers_panel"));
707}
708
709// static
710void LLFloaterIMPanel::onCommitChat(LLUICtrl* caller, void* userdata)
711{
712	LLFloaterIMPanel* self= (LLFloaterIMPanel*) userdata;
713	self->sendMsg();
714}
715
716// static
717void LLFloaterIMPanel::onInputEditorFocusReceived( LLFocusableElement* caller, void* userdata )
718{
719	LLFloaterIMPanel* self= (LLFloaterIMPanel*) userdata;
720	self->mHistoryEditor->setCursorAndScrollToEnd();
721}
722
723// static
724void LLFloaterIMPanel::onInputEditorFocusLost(LLFocusableElement* caller, void* userdata)
725{
726	LLFloaterIMPanel* self = (LLFloaterIMPanel*) userdata;
727	self->setTyping(FALSE);
728}
729
730// static
731void LLFloaterIMPanel::onInputEditorKeystroke(LLLineEditor* caller, void* userdata)
732{
733	LLFloaterIMPanel* self = (LLFloaterIMPanel*)userdata;
734	std::string text = self->mInputEditor->getText();
735	if (!text.empty())
736	{
737		self->setTyping(TRUE);
738	}
739	else
740	{
741		// Deleting all text counts as stopping typing.
742		self->setTyping(FALSE);
743	}
744}
745
746// virtual
747void LLFloaterIMPanel::onClose(bool app_quitting)
748{
749	setTyping(FALSE);
750
751	gIMMgr->leaveSession(mSessionUUID);
752
753	// *HACK hide the voice floater
754	LLFloaterReg::hideInstance("voice_call", mSessionUUID);
755}
756
757void LLFloaterIMPanel::onVisibilityChange(const LLSD& new_visibility)
758{
759	if (new_visibility.asBoolean())
760	{
761		mNumUnreadMessages = 0;
762	}
763	
764	LLVoiceChannel* voice_channel = LLIMModel::getInstance()->getVoiceChannel(mSessionUUID);
765	if (voice_channel && voice_channel->getState() == LLVoiceChannel::STATE_CONNECTED)
766	{
767		if (new_visibility.asBoolean())
768			LLFloaterReg::showInstance("voice_call", mSessionUUID);
769		else
770			LLFloaterReg::hideInstance("voice_call", mSessionUUID);
771	}
772}
773
774void LLFloaterIMPanel::sendMsg()
775{
776	if (!gAgent.isGodlike() 
777		&& (mDialog == IM_NOTHING_SPECIAL)
778		&& mOtherParticipantUUID.isNull())
779	{
780		llinfos << "Cannot send IM to everyone unless you're a god." << llendl;
781		return;
782	}
783
784	if (mInputEditor)
785	{
786		LLWString text = mInputEditor->getConvertedText();
787		if(!text.empty())
788		{
789			// store sent line in history, duplicates will get filtered
790			if (mInputEditor) mInputEditor->updateHistory();
791			// Truncate and convert to UTF8 for transport
792			std::string utf8_text = wstring_to_utf8str(text);
793			utf8_text = utf8str_truncate(utf8_text, MAX_MSG_BUF_SIZE - 1);
794			
795			if ( mSessionInitialized )
796			{
797				LLIMModel::sendMessage(utf8_text,
798								mSessionUUID,
799								mOtherParticipantUUID,
800								mDialog);
801
802			}
803			else
804			{
805				//queue up the message to send once the session is
806				//initialized
807				mQueuedMsgsForInit.append(utf8_text);
808			}
809		}
810
811		LLViewerStats::getInstance()->incStat(LLViewerStats::ST_IM_COUNT);
812
813		mInputEditor->setText(LLStringUtil::null);
814	}
815
816	// Don't need to actually send the typing stop message, the other
817	// client will infer it from receiving the message.
818	mTyping = FALSE;
819	mSentTypingState = TRUE;
820}
821
822void LLFloaterIMPanel::processSessionUpdate(const LLSD& session_update)
823{
824	if (
825		session_update.has("moderated_mode") &&
826		session_update["moderated_mode"].has("voice") )
827	{
828		BOOL voice_moderated = session_update["moderated_mode"]["voice"];
829
830		if (voice_moderated)
831		{
832			setTitle(mSessionLabel + std::string(" ") + getString("moderated_chat_label"));
833		}
834		else
835		{
836			setTitle(mSessionLabel);
837		}
838
839
840		//update the speakers dropdown too, if it's available
841		if (mSpeakerPanel)
842		{
843			mSpeakerPanel->setVoiceModerationCtrlMode(voice_moderated);
844		}
845	}
846}
847
848void LLFloaterIMPanel::sessionInitReplyReceived(const LLUUID& session_id)
849{
850	mSessionUUID = session_id;
851	mSessionInitialized = TRUE;
852
853	//we assume the history editor hasn't moved at all since
854	//we added the starting session message
855	//so, we count how many characters to remove
856	S32 chars_to_remove = mHistoryEditor->getWText().length() -
857		mSessionStartMsgPos;
858	mHistoryEditor->removeTextFromEnd(chars_to_remove);
859
860	//and now, send the queued msg
861	LLSD::array_iterator iter;
862	for ( iter = mQueuedMsgsForInit.beginArray();
863		  iter != mQueuedMsgsForInit.endArray();
864		  ++iter)
865	{
866		LLIMModel::sendMessage(
867			iter->asString(),
868			mSessionUUID,
869			mOtherParticipantUUID,
870			mDialog);
871	}
872}
873
874void LLFloaterIMPanel::setTyping(BOOL typing)
875{
876	LLIMSpeakerMgr* speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(mSessionUUID);
877	if (typing)
878	{
879		// Every time you type something, reset this timer
880		mLastKeystrokeTimer.reset();
881
882		if (!mTyping)
883		{
884			// You just started typing.
885			mFirstKeystrokeTimer.reset();
886
887			// Will send typing state after a short delay.
888			mSentTypingState = FALSE;
889		}
890		
891		if (speaker_mgr)
892			speaker_mgr->setSpeakerTyping(gAgent.getID(), TRUE);
893	}
894	else
895	{
896		if (mTyping)
897		{
898			// you just stopped typing, send state immediately
899			sendTypingState(FALSE);
900			mSentTypingState = TRUE;
901		}
902		if (speaker_mgr)
903			speaker_mgr->setSpeakerTyping(gAgent.getID(), FALSE);
904	}
905
906	mTyping = typing;
907}
908
909void LLFloaterIMPanel::sendTypingState(BOOL typing)
910{
911	// Don't want to send typing indicators to multiple people, potentially too
912	// much network traffic.  Only send in person-to-person IMs.
913	if (mDialog != IM_NOTHING_SPECIAL) return;
914
915	LLIMModel::instance().sendTypingState(mSessionUUID, mOtherParticipantUUID, typing);
916}
917
918
919void LLFloaterIMPanel::processIMTyping(const LLIMInfo* im_info, BOOL typing)
920{
921	if (typing)
922	{
923		// other user started typing
924		addTypingIndicator(im_info->mName);
925	}
926	else
927	{
928		// other user stopped typing
929		removeTypingIndicator(im_info);
930	}
931}
932
933
934void LLFloaterIMPanel::addTypingIndicator(const std::string &name)
935{
936	// we may have lost a "stop-typing" packet, don't add it twice
937	if (!mOtherTyping)
938	{
939		mTypingLineStartIndex = mHistoryEditor->getWText().length();
940		LLUIString typing_start = sTypingStartString;
941		typing_start.setArg("[NAME]", name);
942		addHistoryLine(typing_start, LLUIColorTable::instance().getColor("SystemChatColor"), false);
943		mOtherTypingName = name;
944		mOtherTyping = TRUE;
945	}
946	// MBW -- XXX -- merge from release broke this (argument to this function changed from an LLIMInfo to a name)
947	// Richard will fix.
948//	mSpeakers->setSpeakerTyping(im_info->mFromID, TRUE);
949}
950
951
952void LLFloaterIMPanel::removeTypingIndicator(const LLIMInfo* im_info)
953{
954	if (mOtherTyping)
955	{
956		// Must do this first, otherwise addHistoryLine calls us again.
957		mOtherTyping = FALSE;
958
959		S32 chars_to_remove = mHistoryEditor->getWText().length() - mTypingLineStartIndex;
960		mHistoryEditor->removeTextFromEnd(chars_to_remove);
961		if (im_info)
962		{
963			LLIMSpeakerMgr* speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(mSessionUUID);
964			if (speaker_mgr)
965			{
966				speaker_mgr->setSpeakerTyping(im_info->mFromID, FALSE);
967			}
968		}
969	}
970}
971
972//static 
973void LLFloaterIMPanel::onKickSpeaker(void* user_data)
974{
975
976}