PageRenderTime 684ms CodeModel.GetById 181ms app.highlight 251ms RepoModel.GetById 161ms app.codeStats 1ms

/indra/newview/llchatbar.cpp

https://bitbucket.org/lindenlab/viewer-beta/
C++ | 715 lines | 412 code | 89 blank | 214 comment | 92 complexity | 23eff0f492b04aa5af6d8ebe5b0a0233 MD5 | raw file
  1/** 
  2 * @file llchatbar.cpp
  3 * @brief LLChatBar class implementation
  4 *
  5 * $LicenseInfo:firstyear=2002&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 "llchatbar.h"
 30
 31#include "imageids.h"
 32#include "llfontgl.h"
 33#include "llrect.h"
 34#include "llerror.h"
 35#include "llparcel.h"
 36#include "llstring.h"
 37#include "message.h"
 38#include "llfocusmgr.h"
 39
 40#include "llagent.h"
 41#include "llbutton.h"
 42#include "llcombobox.h"
 43#include "llcommandhandler.h"	// secondlife:///app/chat/ support
 44#include "llviewercontrol.h"
 45#include "llgesturemgr.h"
 46#include "llkeyboard.h"
 47#include "lllineeditor.h"
 48#include "llstatusbar.h"
 49#include "lltextbox.h"
 50#include "lluiconstants.h"
 51#include "llviewergesture.h"			// for triggering gestures
 52#include "llviewermenu.h"		// for deleting object with DEL key
 53#include "llviewerstats.h"
 54#include "llviewerwindow.h"
 55#include "llframetimer.h"
 56#include "llresmgr.h"
 57#include "llworld.h"
 58#include "llinventorymodel.h"
 59#include "llmultigesture.h"
 60#include "llui.h"
 61#include "llviewermenu.h"
 62#include "lluictrlfactory.h"
 63
 64//
 65// Globals
 66//
 67const F32 AGENT_TYPING_TIMEOUT = 5.f;	// seconds
 68
 69LLChatBar *gChatBar = NULL;
 70
 71class LLChatBarGestureObserver : public LLGestureManagerObserver
 72{
 73public:
 74	LLChatBarGestureObserver(LLChatBar* chat_barp) : mChatBar(chat_barp){}
 75	virtual ~LLChatBarGestureObserver() {}
 76	virtual void changed() { mChatBar->refreshGestures(); }
 77private:
 78	LLChatBar* mChatBar;
 79};
 80
 81
 82extern void send_chat_from_viewer(const std::string& utf8_out_text, EChatType type, S32 channel);
 83
 84//
 85// Functions
 86//
 87
 88LLChatBar::LLChatBar() 
 89:	LLPanel(),
 90	mInputEditor(NULL),
 91	mGestureLabelTimer(),
 92	mLastSpecialChatChannel(0),
 93	mIsBuilt(FALSE),
 94	mGestureCombo(NULL),
 95	mObserver(NULL)
 96{
 97	//setIsChrome(TRUE);
 98}
 99
100
101LLChatBar::~LLChatBar()
102{
103	LLGestureMgr::instance().removeObserver(mObserver);
104	delete mObserver;
105	mObserver = NULL;
106	// LLView destructor cleans up children
107}
108
109BOOL LLChatBar::postBuild()
110{
111	getChild<LLUICtrl>("Say")->setCommitCallback(boost::bind(&LLChatBar::onClickSay, this, _1));
112
113	// * NOTE: mantipov: getChild with default parameters returns dummy widget.
114	// Seems this class will be completle removed
115	// attempt to bind to an existing combo box named gesture
116	setGestureCombo(findChild<LLComboBox>( "Gesture"));
117
118	mInputEditor = getChild<LLLineEditor>("Chat Editor");
119	mInputEditor->setKeystrokeCallback(&onInputEditorKeystroke, this);
120	mInputEditor->setFocusLostCallback(boost::bind(&LLChatBar::onInputEditorFocusLost));
121	mInputEditor->setFocusReceivedCallback(boost::bind(&LLChatBar::onInputEditorGainFocus));
122	mInputEditor->setCommitOnFocusLost( FALSE );
123	mInputEditor->setRevertOnEsc( FALSE );
124	mInputEditor->setIgnoreTab(TRUE);
125	mInputEditor->setPassDelete(TRUE);
126	mInputEditor->setReplaceNewlinesWithSpaces(FALSE);
127
128	mInputEditor->setMaxTextLength(DB_CHAT_MSG_STR_LEN);
129	mInputEditor->setEnableLineHistory(TRUE);
130
131	mIsBuilt = TRUE;
132
133	return TRUE;
134}
135
136//-----------------------------------------------------------------------
137// Overrides
138//-----------------------------------------------------------------------
139
140// virtual
141BOOL LLChatBar::handleKeyHere( KEY key, MASK mask )
142{
143	BOOL handled = FALSE;
144
145	if( KEY_RETURN == key )
146	{
147		if (mask == MASK_CONTROL)
148		{
149			// shout
150			sendChat(CHAT_TYPE_SHOUT);
151			handled = TRUE;
152		}
153		else if (mask == MASK_NONE)
154		{
155			// say
156			sendChat( CHAT_TYPE_NORMAL );
157			handled = TRUE;
158		}
159	}
160	// only do this in main chatbar
161	else if ( KEY_ESCAPE == key && gChatBar == this)
162	{
163		stopChat();
164
165		handled = TRUE;
166	}
167
168	return handled;
169}
170
171void LLChatBar::refresh()
172{
173	// HACK: Leave the name of the gesture in place for a few seconds.
174	const F32 SHOW_GESTURE_NAME_TIME = 2.f;
175	if (mGestureLabelTimer.getStarted() && mGestureLabelTimer.getElapsedTimeF32() > SHOW_GESTURE_NAME_TIME)
176	{
177		LLCtrlListInterface* gestures = mGestureCombo ? mGestureCombo->getListInterface() : NULL;
178		if (gestures) gestures->selectFirstItem();
179		mGestureLabelTimer.stop();
180	}
181
182	if ((gAgent.getTypingTime() > AGENT_TYPING_TIMEOUT) && (gAgent.getRenderState() & AGENT_STATE_TYPING))
183	{
184		gAgent.stopTyping();
185	}
186
187	getChildView("Say")->setEnabled(mInputEditor->getText().size() > 0);
188
189}
190
191void LLChatBar::refreshGestures()
192{
193	if (mGestureCombo)
194	{
195		//store current selection so we can maintain it
196		std::string cur_gesture = mGestureCombo->getValue().asString();
197		mGestureCombo->selectFirstItem();
198		std::string label = mGestureCombo->getValue().asString();;
199		// clear
200		mGestureCombo->clearRows();
201
202		// collect list of unique gestures
203		std::map <std::string, BOOL> unique;
204		LLGestureMgr::item_map_t::const_iterator it;
205		const LLGestureMgr::item_map_t& active_gestures = LLGestureMgr::instance().getActiveGestures();
206		for (it = active_gestures.begin(); it != active_gestures.end(); ++it)
207		{
208			LLMultiGesture* gesture = (*it).second;
209			if (gesture)
210			{
211				if (!gesture->mTrigger.empty())
212				{
213					unique[gesture->mTrigger] = TRUE;
214				}
215			}
216		}
217
218		// add unique gestures
219		std::map <std::string, BOOL>::iterator it2;
220		for (it2 = unique.begin(); it2 != unique.end(); ++it2)
221		{
222			mGestureCombo->addSimpleElement((*it2).first);
223		}
224		
225		mGestureCombo->sortByName();
226		// Insert label after sorting, at top, with separator below it
227		mGestureCombo->addSeparator(ADD_TOP);
228		mGestureCombo->addSimpleElement(getString("gesture_label"), ADD_TOP);
229		
230		if (!cur_gesture.empty())
231		{ 
232			mGestureCombo->selectByValue(LLSD(cur_gesture));
233		}
234		else
235		{
236			mGestureCombo->selectFirstItem();
237		}
238	}
239}
240
241// Move the cursor to the correct input field.
242void LLChatBar::setKeyboardFocus(BOOL focus)
243{
244	if (focus)
245	{
246		if (mInputEditor)
247		{
248			mInputEditor->setFocus(TRUE);
249			mInputEditor->selectAll();
250		}
251	}
252	else if (gFocusMgr.childHasKeyboardFocus(this))
253	{
254		if (mInputEditor)
255		{
256			mInputEditor->deselect();
257		}
258		setFocus(FALSE);
259	}
260}
261
262
263// Ignore arrow keys in chat bar
264void LLChatBar::setIgnoreArrowKeys(BOOL b)
265{
266	if (mInputEditor)
267	{
268		mInputEditor->setIgnoreArrowKeys(b);
269	}
270}
271
272BOOL LLChatBar::inputEditorHasFocus()
273{
274	return mInputEditor && mInputEditor->hasFocus();
275}
276
277std::string LLChatBar::getCurrentChat()
278{
279	return mInputEditor ? mInputEditor->getText() : LLStringUtil::null;
280}
281
282void LLChatBar::setGestureCombo(LLComboBox* combo)
283{
284	mGestureCombo = combo;
285	if (mGestureCombo)
286	{
287		mGestureCombo->setCommitCallback(boost::bind(&LLChatBar::onCommitGesture, this, _1));
288
289		// now register observer since we have a place to put the results
290		mObserver = new LLChatBarGestureObserver(this);
291		LLGestureMgr::instance().addObserver(mObserver);
292
293		// refresh list from current active gestures
294		refreshGestures();
295	}
296}
297
298//-----------------------------------------------------------------------
299// Internal functions
300//-----------------------------------------------------------------------
301
302// If input of the form "/20foo" or "/20 foo", returns "foo" and channel 20.
303// Otherwise returns input and channel 0.
304LLWString LLChatBar::stripChannelNumber(const LLWString &mesg, S32* channel)
305{
306	if (mesg[0] == '/'
307		&& mesg[1] == '/')
308	{
309		// This is a "repeat channel send"
310		*channel = mLastSpecialChatChannel;
311		return mesg.substr(2, mesg.length() - 2);
312	}
313	else if (mesg[0] == '/'
314			 && mesg[1]
315			 && LLStringOps::isDigit(mesg[1]))
316	{
317		// This a special "/20" speak on a channel
318		S32 pos = 0;
319
320		// Copy the channel number into a string
321		LLWString channel_string;
322		llwchar c;
323		do
324		{
325			c = mesg[pos+1];
326			channel_string.push_back(c);
327			pos++;
328		}
329		while(c && pos < 64 && LLStringOps::isDigit(c));
330		
331		// Move the pointer forward to the first non-whitespace char
332		// Check isspace before looping, so we can handle "/33foo"
333		// as well as "/33 foo"
334		while(c && iswspace(c))
335		{
336			c = mesg[pos+1];
337			pos++;
338		}
339		
340		mLastSpecialChatChannel = strtol(wstring_to_utf8str(channel_string).c_str(), NULL, 10);
341		*channel = mLastSpecialChatChannel;
342		return mesg.substr(pos, mesg.length() - pos);
343	}
344	else
345	{
346		// This is normal chat.
347		*channel = 0;
348		return mesg;
349	}
350}
351
352
353void LLChatBar::sendChat( EChatType type )
354{
355	if (mInputEditor)
356	{
357		LLWString text = mInputEditor->getConvertedText();
358		if (!text.empty())
359		{
360			// store sent line in history, duplicates will get filtered
361			if (mInputEditor) mInputEditor->updateHistory();
362			// Check if this is destined for another channel
363			S32 channel = 0;
364			stripChannelNumber(text, &channel);
365			
366			std::string utf8text = wstring_to_utf8str(text);
367			// Try to trigger a gesture, if not chat to a script.
368			std::string utf8_revised_text;
369			if (0 == channel)
370			{
371				// discard returned "found" boolean
372				LLGestureMgr::instance().triggerAndReviseString(utf8text, &utf8_revised_text);
373			}
374			else
375			{
376				utf8_revised_text = utf8text;
377			}
378
379			utf8_revised_text = utf8str_trim(utf8_revised_text);
380
381			if (!utf8_revised_text.empty())
382			{
383				// Chat with animation
384				sendChatFromViewer(utf8_revised_text, type, TRUE);
385			}
386		}
387	}
388
389	getChild<LLUICtrl>("Chat Editor")->setValue(LLStringUtil::null);
390
391	gAgent.stopTyping();
392
393	// If the user wants to stop chatting on hitting return, lose focus
394	// and go out of chat mode.
395	if (gChatBar == this && gSavedSettings.getBOOL("CloseChatOnReturn"))
396	{
397		stopChat();
398	}
399}
400
401
402//-----------------------------------------------------------------------
403// Static functions
404//-----------------------------------------------------------------------
405
406// static 
407void LLChatBar::startChat(const char* line)
408{
409	//TODO* remove DUMMY chat
410	//if(gBottomTray && gBottomTray->getChatBox())
411	//{
412	//	gBottomTray->setVisible(TRUE);
413	//	gBottomTray->getChatBox()->setFocus(TRUE);
414	//}
415
416	// *TODO Vadim: Why was this code commented out?
417
418// 	gChatBar->setVisible(TRUE);
419// 	gChatBar->setKeyboardFocus(TRUE);
420// 	gSavedSettings.setBOOL("ChatVisible", TRUE);
421// 
422// 	if (line && gChatBar->mInputEditor)
423// 	{
424// 		std::string line_string(line);
425// 		gChatBar->mInputEditor->setText(line_string);
426// 	}
427// 	// always move cursor to end so users don't obliterate chat when accidentally hitting WASD
428// 	gChatBar->mInputEditor->setCursorToEnd();
429}
430
431
432// Exit "chat mode" and do the appropriate focus changes
433// static
434void LLChatBar::stopChat()
435{
436	//TODO* remove DUMMY chat
437	//if(gBottomTray && gBottomTray->getChatBox())
438	///{
439	//	gBottomTray->getChatBox()->setFocus(FALSE);
440	//}
441
442	// *TODO Vadim: Why was this code commented out?
443
444// 	// In simple UI mode, we never release focus from the chat bar
445// 	gChatBar->setKeyboardFocus(FALSE);
446// 
447// 	// If we typed a movement key and pressed return during the
448// 	// same frame, the keyboard handlers will see the key as having
449// 	// gone down this frame and try to move the avatar.
450// 	gKeyboard->resetKeys();
451// 	gKeyboard->resetMaskKeys();
452// 
453// 	// stop typing animation
454// 	gAgent.stopTyping();
455// 
456// 	// hide chat bar so it doesn't grab focus back
457// 	gChatBar->setVisible(FALSE);
458// 	gSavedSettings.setBOOL("ChatVisible", FALSE);
459}
460
461// static
462void LLChatBar::onInputEditorKeystroke( LLLineEditor* caller, void* userdata )
463{
464	LLChatBar* self = (LLChatBar *)userdata;
465
466	LLWString raw_text;
467	if (self->mInputEditor) raw_text = self->mInputEditor->getWText();
468
469	// Can't trim the end, because that will cause autocompletion
470	// to eat trailing spaces that might be part of a gesture.
471	LLWStringUtil::trimHead(raw_text);
472
473	S32 length = raw_text.length();
474
475	if( (length > 0) && (raw_text[0] != '/') )  // forward slash is used for escape (eg. emote) sequences
476	{
477		gAgent.startTyping();
478	}
479	else
480	{
481		gAgent.stopTyping();
482	}
483
484	/* Doesn't work -- can't tell the difference between a backspace
485	   that killed the selection vs. backspace at the end of line.
486	if (length > 1 
487		&& text[0] == '/'
488		&& key == KEY_BACKSPACE)
489	{
490		// the selection will already be deleted, but we need to trim
491		// off the character before
492		std::string new_text = raw_text.substr(0, length-1);
493		self->mInputEditor->setText( new_text );
494		self->mInputEditor->setCursorToEnd();
495		length = length - 1;
496	}
497	*/
498
499	KEY key = gKeyboard->currentKey();
500
501	// Ignore "special" keys, like backspace, arrows, etc.
502	if (length > 1 
503		&& raw_text[0] == '/'
504		&& key < KEY_SPECIAL)
505	{
506		// we're starting a gesture, attempt to autocomplete
507
508		std::string utf8_trigger = wstring_to_utf8str(raw_text);
509		std::string utf8_out_str(utf8_trigger);
510
511		if (LLGestureMgr::instance().matchPrefix(utf8_trigger, &utf8_out_str))
512		{
513			if (self->mInputEditor)
514			{
515				std::string rest_of_match = utf8_out_str.substr(utf8_trigger.size());
516				self->mInputEditor->setText(utf8_trigger + rest_of_match); // keep original capitalization for user-entered part
517				S32 outlength = self->mInputEditor->getLength(); // in characters
518			
519				// Select to end of line, starting from the character
520				// after the last one the user typed.
521				self->mInputEditor->setSelection(length, outlength);
522			}
523		}
524
525		//llinfos << "GESTUREDEBUG " << trigger 
526		//	<< " len " << length
527		//	<< " outlen " << out_str.getLength()
528		//	<< llendl;
529	}
530}
531
532// static
533void LLChatBar::onInputEditorFocusLost()
534{
535	// stop typing animation
536	gAgent.stopTyping();
537}
538
539// static
540void LLChatBar::onInputEditorGainFocus()
541{
542	//LLFloaterChat::setHistoryCursorAndScrollToEnd();
543}
544
545void LLChatBar::onClickSay( LLUICtrl* ctrl )
546{
547	std::string cmd = ctrl->getValue().asString();
548	e_chat_type chat_type = CHAT_TYPE_NORMAL;
549	if (cmd == "shout")
550	{
551		chat_type = CHAT_TYPE_SHOUT;
552	}
553	else if (cmd == "whisper")
554	{
555		chat_type = CHAT_TYPE_WHISPER;
556	}
557	sendChat(chat_type);
558}
559
560void LLChatBar::sendChatFromViewer(const std::string &utf8text, EChatType type, BOOL animate)
561{
562	sendChatFromViewer(utf8str_to_wstring(utf8text), type, animate);
563}
564
565void LLChatBar::sendChatFromViewer(const LLWString &wtext, EChatType type, BOOL animate)
566{
567	// as soon as we say something, we no longer care about teaching the user
568	// how to chat
569	gWarningSettings.setBOOL("FirstOtherChatBeforeUser", FALSE);
570	
571	// Look for "/20 foo" channel chats.
572	S32 channel = 0;
573	LLWString out_text = stripChannelNumber(wtext, &channel);
574	std::string utf8_out_text = wstring_to_utf8str(out_text);
575	if (!utf8_out_text.empty())
576	{
577		utf8_out_text = utf8str_truncate(utf8_out_text, MAX_MSG_STR_LEN);
578	}
579
580	std::string utf8_text = wstring_to_utf8str(wtext);
581	utf8_text = utf8str_trim(utf8_text);
582	if (!utf8_text.empty())
583	{
584		utf8_text = utf8str_truncate(utf8_text, MAX_STRING - 1);
585	}
586
587	// Don't animate for chats people can't hear (chat to scripts)
588	if (animate && (channel == 0))
589	{
590		if (type == CHAT_TYPE_WHISPER)
591		{
592			lldebugs << "You whisper " << utf8_text << llendl;
593			gAgent.sendAnimationRequest(ANIM_AGENT_WHISPER, ANIM_REQUEST_START);
594		}
595		else if (type == CHAT_TYPE_NORMAL)
596		{
597			lldebugs << "You say " << utf8_text << llendl;
598			gAgent.sendAnimationRequest(ANIM_AGENT_TALK, ANIM_REQUEST_START);
599		}
600		else if (type == CHAT_TYPE_SHOUT)
601		{
602			lldebugs << "You shout " << utf8_text << llendl;
603			gAgent.sendAnimationRequest(ANIM_AGENT_SHOUT, ANIM_REQUEST_START);
604		}
605		else
606		{
607			llinfos << "send_chat_from_viewer() - invalid volume" << llendl;
608			return;
609		}
610	}
611	else
612	{
613		if (type != CHAT_TYPE_START && type != CHAT_TYPE_STOP)
614		{
615			lldebugs << "Channel chat: " << utf8_text << llendl;
616		}
617	}
618
619	send_chat_from_viewer(utf8_out_text, type, channel);
620}
621/*
622void send_chat_from_viewer(const std::string& utf8_out_text, EChatType type, S32 channel)
623{
624	LLMessageSystem* msg = gMessageSystem;
625	msg->newMessageFast(_PREHASH_ChatFromViewer);
626	msg->nextBlockFast(_PREHASH_AgentData);
627	msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID());
628	msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID());
629	msg->nextBlockFast(_PREHASH_ChatData);
630	msg->addStringFast(_PREHASH_Message, utf8_out_text);
631	msg->addU8Fast(_PREHASH_Type, type);
632	msg->addS32("Channel", channel);
633
634	gAgent.sendReliableMessage();
635
636	LLViewerStats::getInstance()->incStat(LLViewerStats::ST_CHAT_COUNT);
637}
638*/
639
640void LLChatBar::onCommitGesture(LLUICtrl* ctrl)
641{
642	LLCtrlListInterface* gestures = mGestureCombo ? mGestureCombo->getListInterface() : NULL;
643	if (gestures)
644	{
645		S32 index = gestures->getFirstSelectedIndex();
646		if (index == 0)
647		{
648			return;
649		}
650		const std::string& trigger = gestures->getSelectedValue().asString();
651
652		// pretend the user chatted the trigger string, to invoke
653		// substitution and logging.
654		std::string text(trigger);
655		std::string revised_text;
656		LLGestureMgr::instance().triggerAndReviseString(text, &revised_text);
657
658		revised_text = utf8str_trim(revised_text);
659		if (!revised_text.empty())
660		{
661			// Don't play nodding animation
662			sendChatFromViewer(revised_text, CHAT_TYPE_NORMAL, FALSE);
663		}
664	}
665	mGestureLabelTimer.start();
666	if (mGestureCombo != NULL)
667	{
668		// free focus back to chat bar
669		mGestureCombo->setFocus(FALSE);
670	}
671}
672
673
674/* Cruft - global gChatHandler declared below has been commented out,
675   so this class is never used.  See similar code in llnearbychatbar.cpp
676class LLChatHandler : public LLCommandHandler
677{
678public:
679	// not allowed from outside the app
680	LLChatHandler() : LLCommandHandler("chat", UNTRUSTED_BLOCK) { }
681
682    // Your code here
683	bool handle(const LLSD& tokens, const LLSD& query_map,
684				LLMediaCtrl* web)
685	{
686		bool retval = false;
687		// Need at least 2 tokens to have a valid message.
688		if (tokens.size() < 2) 
689		{
690			retval = false;
691		}
692		else
693		{
694		S32 channel = tokens[0].asInteger();
695			// VWR-19499 Restrict function to chat channels greater than 0.
696			if ((channel > 0) && (channel < CHAT_CHANNEL_DEBUG))
697			{
698				retval = true;
699				// Say mesg on channel
700		std::string mesg = tokens[1].asString();
701		send_chat_from_viewer(mesg, CHAT_TYPE_NORMAL, channel);
702			}
703			else
704			{
705				retval = false;
706				// Tell us this is an unsupported SLurl.
707			}
708		}
709		return retval;
710	}
711};
712
713// Creating the object registers with the dispatcher.
714//LLChatHandler gChatHandler;
715cruft */