PageRenderTime 1029ms CodeModel.GetById 215ms app.highlight 207ms RepoModel.GetById 600ms app.codeStats 1ms

/indra/newview/llnearbychathandler.cpp

https://bitbucket.org/lindenlab/viewer-beta/
C++ | 632 lines | 401 code | 134 blank | 97 comment | 75 complexity | 71cf0730b8783d25b7ceffdb464836f3 MD5 | raw file
  1/** 
  2 * @file LLNearbyChatHandler.cpp
  3 * @brief Nearby chat notification managment
  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 "llagentdata.h" // for gAgentID
 30#include "llnearbychathandler.h"
 31
 32#include "llchatitemscontainerctrl.h"
 33#include "llfirstuse.h"
 34#include "llfloaterscriptdebug.h"
 35#include "llhints.h"
 36#include "llnearbychat.h"
 37#include "llrecentpeople.h"
 38
 39#include "llviewercontrol.h"
 40
 41#include "llfloaterreg.h"//for LLFloaterReg::getTypedInstance
 42#include "llviewerwindow.h"//for screen channel position
 43#include "llnearbychatbar.h"
 44#include "llrootview.h"
 45#include "lllayoutstack.h"
 46
 47//add LLNearbyChatHandler to LLNotificationsUI namespace
 48using namespace LLNotificationsUI;
 49
 50//-----------------------------------------------------------------------------------------------
 51//LLNearbyChatScreenChannel
 52//-----------------------------------------------------------------------------------------------	
 53LLToastPanelBase* createToastPanel()
 54{
 55	LLNearbyChatToastPanel* item = LLNearbyChatToastPanel::createInstance();
 56	return item;
 57}
 58
 59class LLNearbyChatScreenChannel: public LLScreenChannelBase
 60{
 61	LOG_CLASS(LLNearbyChatScreenChannel);
 62public:
 63	typedef std::vector<LLHandle<LLToast> > toast_vec_t;
 64	typedef std::list<LLHandle<LLToast> > toast_list_t;
 65
 66	LLNearbyChatScreenChannel(const Params& p)
 67		: LLScreenChannelBase(p)
 68	{
 69		mStopProcessing = false;
 70
 71		LLControlVariable* ctrl = gSavedSettings.getControl("NearbyToastLifeTime").get();
 72		if (ctrl)
 73		{
 74			ctrl->getSignal()->connect(boost::bind(&LLNearbyChatScreenChannel::updateToastsLifetime, this));
 75		}
 76
 77		ctrl = gSavedSettings.getControl("NearbyToastFadingTime").get();
 78		if (ctrl)
 79		{
 80			ctrl->getSignal()->connect(boost::bind(&LLNearbyChatScreenChannel::updateToastFadingTime, this));
 81		}
 82	}
 83
 84	void addNotification	(LLSD& notification);
 85	void arrangeToasts		();
 86	
 87	typedef boost::function<LLToastPanelBase* (void )> create_toast_panel_callback_t;
 88	void setCreatePanelCallback(create_toast_panel_callback_t value) { m_create_toast_panel_callback_t = value;}
 89
 90	void onToastDestroyed	(LLToast* toast, bool app_quitting);
 91	void onToastFade		(LLToast* toast);
 92
 93	void redrawToasts()
 94	{
 95		arrangeToasts();
 96	}
 97
 98	// hide all toasts from screen, but not remove them from a channel
 99	// removes all toasts from a channel
100	virtual void		removeToastsFromChannel() 
101	{
102		for(toast_vec_t::iterator it = m_active_toasts.begin(); it != m_active_toasts.end(); ++it)
103		{
104			addToToastPool(it->get());
105		}
106		m_active_toasts.clear();
107	};
108
109	virtual void deleteAllChildren()
110	{
111		LL_DEBUGS("NearbyChat") << "Clearing toast pool" << llendl;
112		m_toast_pool.clear();
113		m_active_toasts.clear();
114		LLScreenChannelBase::deleteAllChildren();
115	}
116
117protected:
118	void	deactivateToast(LLToast* toast);
119	void	addToToastPool(LLToast* toast)
120	{
121		if (!toast) return;
122		LL_DEBUGS("NearbyChat") << "Pooling toast" << llendl;
123		toast->setVisible(FALSE);
124		toast->stopTimer();
125		toast->setIsHidden(true);
126
127		// Nearby chat toasts that are hidden, not destroyed. They are collected to the toast pool, so that
128		// they can be used next time, this is done for performance. But if the toast lifetime was changed
129		// (from preferences floater (STORY-36)) while it was shown (at this moment toast isn't in the pool yet)
130		// changes don't take affect.
131		// So toast's lifetime should be updated each time it's added to the pool. Otherwise viewer would have
132		// to be restarted so that changes take effect.
133		toast->setLifetime(gSavedSettings.getS32("NearbyToastLifeTime"));
134		toast->setFadingTime(gSavedSettings.getS32("NearbyToastFadingTime"));
135		m_toast_pool.push_back(toast->getHandle());
136	}
137
138	void	createOverflowToast(S32 bottom, F32 timer);
139
140	void 	updateToastsLifetime();
141
142	void	updateToastFadingTime();
143
144	create_toast_panel_callback_t m_create_toast_panel_callback_t;
145
146	bool	createPoolToast();
147	
148	toast_vec_t m_active_toasts;
149	toast_list_t m_toast_pool;
150
151	bool	mStopProcessing;
152	bool	mChannelRect;
153};
154
155//-----------------------------------------------------------------------------------------------
156// LLNearbyChatToast
157//-----------------------------------------------------------------------------------------------
158
159// We're deriving from LLToast to be able to override onClose()
160// in order to handle closing nearby chat toasts properly.
161class LLNearbyChatToast : public LLToast
162{
163	LOG_CLASS(LLNearbyChatToast);
164public:
165	LLNearbyChatToast(const LLToast::Params& p, LLNearbyChatScreenChannel* nc_channelp)
166	:	LLToast(p),
167	 	mNearbyChatScreenChannelp(nc_channelp)
168	{
169	}
170
171	/*virtual*/ void onClose(bool app_quitting);
172
173private:
174	LLNearbyChatScreenChannel*	mNearbyChatScreenChannelp;
175};
176
177//-----------------------------------------------------------------------------------------------
178// LLNearbyChatScreenChannel
179//-----------------------------------------------------------------------------------------------
180
181void LLNearbyChatScreenChannel::deactivateToast(LLToast* toast)
182{
183	toast_vec_t::iterator pos = std::find(m_active_toasts.begin(), m_active_toasts.end(), toast->getHandle());
184
185	if (pos == m_active_toasts.end())
186	{
187		llassert(pos == m_active_toasts.end());
188		return;
189	}
190
191	LL_DEBUGS("NearbyChat") << "Deactivating toast" << llendl;
192	m_active_toasts.erase(pos);
193}
194
195void	LLNearbyChatScreenChannel::createOverflowToast(S32 bottom, F32 timer)
196{
197	//we don't need overflow toast in nearby chat
198}
199
200void LLNearbyChatScreenChannel::onToastDestroyed(LLToast* toast, bool app_quitting)
201{	
202	LL_DEBUGS("NearbyChat") << "Toast destroyed (app_quitting=" << app_quitting << ")" << llendl;
203
204	if (app_quitting)
205	{
206		// Viewer is quitting.
207		// Immediately stop processing chat messages (EXT-1419).
208	mStopProcessing = true;
209}
210	else
211	{
212		// The toast is being closed by user (STORM-192).
213		// Remove it from the list of active toasts to prevent
214		// further references to the invalid pointer.
215		deactivateToast(toast);
216	}
217}
218
219void LLNearbyChatScreenChannel::onToastFade(LLToast* toast)
220{	
221	LL_DEBUGS("NearbyChat") << "Toast fading" << llendl;
222
223	//fade mean we put toast to toast pool
224	if(!toast)
225		return;
226
227	deactivateToast(toast);
228
229	addToToastPool(toast);
230	
231	arrangeToasts();
232}
233
234void LLNearbyChatScreenChannel::updateToastsLifetime()
235{
236	S32 seconds = gSavedSettings.getS32("NearbyToastLifeTime");
237	toast_list_t::iterator it;
238
239	for(it = m_toast_pool.begin(); it != m_toast_pool.end(); ++it)
240	{
241		(*it).get()->setLifetime(seconds);
242	}
243}
244
245void LLNearbyChatScreenChannel::updateToastFadingTime()
246{
247	S32 seconds = gSavedSettings.getS32("NearbyToastFadingTime");
248	toast_list_t::iterator it;
249
250	for(it = m_toast_pool.begin(); it != m_toast_pool.end(); ++it)
251	{
252		(*it).get()->setFadingTime(seconds);
253	}
254}
255
256bool	LLNearbyChatScreenChannel::createPoolToast()
257{
258	LLToastPanelBase* panel= m_create_toast_panel_callback_t();
259	if(!panel)
260		return false;
261	
262	LLToast::Params p;
263	p.panel = panel;
264	p.lifetime_secs = gSavedSettings.getS32("NearbyToastLifeTime");
265	p.fading_time_secs = gSavedSettings.getS32("NearbyToastFadingTime");
266
267	LLToast* toast = new LLNearbyChatToast(p, this);
268	
269	
270	toast->setOnFadeCallback(boost::bind(&LLNearbyChatScreenChannel::onToastFade, this, _1));
271
272	// If the toast gets somehow prematurely destroyed, deactivate it to prevent crash (STORM-1352).
273	toast->setOnToastDestroyedCallback(boost::bind(&LLNearbyChatScreenChannel::onToastDestroyed, this, _1, false));
274
275	LL_DEBUGS("NearbyChat") << "Creating and pooling toast" << llendl;	
276	m_toast_pool.push_back(toast->getHandle());
277	return true;
278}
279
280void LLNearbyChatScreenChannel::addNotification(LLSD& notification)
281{
282	//look in pool. if there is any message
283	if(mStopProcessing)
284		return;
285
286	/*
287    find last toast and check ID
288	*/
289
290	if(m_active_toasts.size())
291	{
292		LLUUID fromID = notification["from_id"].asUUID();		// agent id or object id
293		std::string from = notification["from"].asString();
294		LLToast* toast = m_active_toasts[0].get();
295		if (toast)
296		{
297			LLNearbyChatToastPanel* panel = dynamic_cast<LLNearbyChatToastPanel*>(toast->getPanel());
298  
299			if(panel && panel->messageID() == fromID && panel->getFromName() == from && panel->canAddText())
300			{
301				panel->addMessage(notification);
302				toast->reshapeToPanel();
303				toast->startTimer();
304	  
305				arrangeToasts();
306				return;
307			}
308		}
309	}
310	
311
312	
313	if(m_toast_pool.empty())
314	{
315		//"pool" is empty - create one more panel
316		LL_DEBUGS("NearbyChat") << "Empty pool" << llendl;
317		if(!createPoolToast())//created toast will go to pool. so next call will find it
318			return;
319		addNotification(notification);
320		return;
321	}
322
323	int chat_type = notification["chat_type"].asInteger();
324	
325	if( ((EChatType)chat_type == CHAT_TYPE_DEBUG_MSG))
326	{
327		if(gSavedSettings.getBOOL("ShowScriptErrors") == FALSE) 
328			return;
329		if(gSavedSettings.getS32("ShowScriptErrorsLocation")== 1)
330			return;
331	}
332		
333
334	//take 1st element from pool, (re)initialize it, put it in active toasts
335
336	LL_DEBUGS("NearbyChat") << "Getting toast from pool" << llendl;
337	LLToast* toast = m_toast_pool.back().get();
338
339	m_toast_pool.pop_back();
340
341
342	LLToastPanelBase* panel = dynamic_cast<LLToastPanelBase*>(toast->getPanel());
343	if(!panel)
344		return;
345	panel->init(notification);
346
347	toast->reshapeToPanel();
348	toast->startTimer();
349	
350	m_active_toasts.push_back(toast->getHandle());
351
352	arrangeToasts();
353}
354
355static bool sort_toasts_predicate(LLHandle<LLToast> first, LLHandle<LLToast> second)
356{
357	if (!first.get() || !second.get()) return false; // STORM-1352
358
359	F32 v1 = first.get()->getTimeLeftToLive();
360	F32 v2 = second.get()->getTimeLeftToLive();
361	return v1 > v2;
362}
363
364void LLNearbyChatScreenChannel::arrangeToasts()
365{
366	if(mStopProcessing || isHovering())
367		return;
368
369	if (mFloaterSnapRegion == NULL)
370	{
371		mFloaterSnapRegion = gViewerWindow->getRootView()->getChildView("floater_snap_region");
372	}
373	
374	if (!getParent())
375	{
376		// connect to floater snap region just to get resize events, we don't care about being a proper widget 
377		mFloaterSnapRegion->addChild(this);
378		setFollows(FOLLOWS_ALL);
379	}
380
381	LLRect	toast_rect;	
382	updateRect();
383
384	LLRect channel_rect;
385	mFloaterSnapRegion->localRectToOtherView(mFloaterSnapRegion->getLocalRect(), &channel_rect, gFloaterView);
386	channel_rect.mLeft += 10;
387	channel_rect.mRight = channel_rect.mLeft + 300;
388
389	S32 channel_bottom = channel_rect.mBottom;
390
391	S32		bottom = channel_bottom + 80;
392	S32		margin = gSavedSettings.getS32("ToastGap");
393
394	//sort active toasts
395	std::sort(m_active_toasts.begin(),m_active_toasts.end(),sort_toasts_predicate);
396
397	//calc max visible item and hide other toasts.
398
399	for(toast_vec_t::iterator it = m_active_toasts.begin(); it != m_active_toasts.end(); ++it)
400	{
401		LLToast* toast = it->get();
402		if (!toast)
403		{
404			llwarns << "NULL found in the active chat toasts list!" << llendl;
405			continue;
406		}
407
408		S32 toast_top = bottom + toast->getRect().getHeight() + margin;
409
410		if(toast_top > channel_rect.getHeight())
411		{
412			while(it!=m_active_toasts.end())
413			{
414				addToToastPool(it->get());
415				it=m_active_toasts.erase(it);
416			}
417			break;
418		}
419
420		toast_rect = toast->getRect();
421		toast_rect.setLeftTopAndSize(channel_rect.mLeft , bottom + toast_rect.getHeight(), toast_rect.getWidth() ,toast_rect.getHeight());
422
423		toast->setRect(toast_rect);
424		bottom += toast_rect.getHeight() - toast->getTopPad() + margin;
425	}
426	
427	// use reverse order to provide correct z-order and avoid toast blinking
428	
429	for(toast_vec_t::reverse_iterator it = m_active_toasts.rbegin(); it != m_active_toasts.rend(); ++it)
430	{
431		LLToast* toast = it->get();
432		if (toast)
433	{
434		toast->setIsHidden(false);
435		toast->setVisible(TRUE);
436		}
437	}
438
439}
440
441
442
443//-----------------------------------------------------------------------------------------------
444//LLNearbyChatHandler
445//-----------------------------------------------------------------------------------------------
446boost::scoped_ptr<LLEventPump> LLNearbyChatHandler::sChatWatcher(new LLEventStream("LLChat"));
447
448LLNearbyChatHandler::LLNearbyChatHandler(e_notification_type type, const LLSD& id)
449{
450	mType = type;
451
452	// Getting a Channel for our notifications
453	LLNearbyChatScreenChannel::Params p;
454	p.id = LLUUID(gSavedSettings.getString("NearByChatChannelUUID"));
455	LLNearbyChatScreenChannel* channel = new LLNearbyChatScreenChannel(p);
456	
457	LLNearbyChatScreenChannel::create_toast_panel_callback_t callback = createToastPanel;
458
459	channel->setCreatePanelCallback(callback);
460
461	mChannel = LLChannelManager::getInstance()->addChannel(channel);
462}
463
464LLNearbyChatHandler::~LLNearbyChatHandler()
465{
466}
467
468
469void LLNearbyChatHandler::initChannel()
470{
471	//LLRect snap_rect = gFloaterView->getSnapRect();
472	//mChannel->init(snap_rect.mLeft, snap_rect.mLeft + 200);
473}
474
475
476
477void LLNearbyChatHandler::processChat(const LLChat& chat_msg,
478									  const LLSD &args)
479{
480	if(chat_msg.mMuted == TRUE)
481		return;
482
483	if(chat_msg.mText.empty())
484		return;//don't process empty messages
485
486	LLFloater* chat_bar = LLFloaterReg::getInstance("chat_bar");
487
488	LLNearbyChat* nearby_chat = chat_bar->findChild<LLNearbyChat>("nearby_chat");
489
490	// Build notification data 
491	LLSD notification;
492	notification["message"] = chat_msg.mText;
493	notification["from"] = chat_msg.mFromName;
494	notification["from_id"] = chat_msg.mFromID;
495	notification["time"] = chat_msg.mTime;
496	notification["source"] = (S32)chat_msg.mSourceType;
497	notification["chat_type"] = (S32)chat_msg.mChatType;
498	notification["chat_style"] = (S32)chat_msg.mChatStyle;
499	// Pass sender info so that it can be rendered properly (STORM-1021).
500	notification["sender_slurl"] = LLViewerChat::getSenderSLURL(chat_msg, args);
501
502	if (chat_msg.mChatType == CHAT_TYPE_DIRECT &&
503		chat_msg.mText.length() > 0 &&
504		chat_msg.mText[0] == '@')
505	{
506		// Send event on to LLEventStream and exit
507		sChatWatcher->post(notification);
508		return;
509	}
510
511	// don't show toast and add message to chat history on receive debug message
512	// with disabled setting showing script errors or enabled setting to show script
513	// errors in separate window.
514	if (chat_msg.mChatType == CHAT_TYPE_DEBUG_MSG)
515	{
516		if(gSavedSettings.getBOOL("ShowScriptErrors") == FALSE)
517			return;
518
519		// don't process debug messages from not owned objects, see EXT-7762
520		if (gAgentID != chat_msg.mOwnerID)
521		{
522			return;
523		}
524
525		if (gSavedSettings.getS32("ShowScriptErrorsLocation")== 1)// show error in window //("ScriptErrorsAsChat"))
526		{
527
528			LLColor4 txt_color;
529
530			LLViewerChat::getChatColor(chat_msg,txt_color);
531
532			LLFloaterScriptDebug::addScriptLine(chat_msg.mText,
533												chat_msg.mFromName,
534												txt_color,
535												chat_msg.mFromID);
536			return;
537		}
538	}
539
540	nearby_chat->addMessage(chat_msg, true, args);
541
542	if(chat_msg.mSourceType == CHAT_SOURCE_AGENT 
543		&& chat_msg.mFromID.notNull() 
544		&& chat_msg.mFromID != gAgentID)
545	{
546 		LLFirstUse::otherAvatarChatFirst();
547
548 		// Add sender to the recent people list.
549 		LLRecentPeople::instance().add(chat_msg.mFromID);
550
551	}
552
553	// Send event on to LLEventStream
554	sChatWatcher->post(notification);
555
556
557	if( !chat_bar->isMinimized()
558		&& nearby_chat->isInVisibleChain() 
559		|| ( chat_msg.mSourceType == CHAT_SOURCE_AGENT
560			&& gSavedSettings.getBOOL("UseChatBubbles") )
561		|| !mChannel->getShowToasts() ) // to prevent toasts in Busy mode
562		return;//no need in toast if chat is visible or if bubble chat is enabled
563
564	// arrange a channel on a screen
565	if(!mChannel->getVisible())
566	{
567		initChannel();
568	}
569
570	/*
571	//comment all this due to EXT-4432
572	..may clean up after some time...
573
574	//only messages from AGENTS
575	if(CHAT_SOURCE_OBJECT == chat_msg.mSourceType)
576	{
577		if(chat_msg.mChatType == CHAT_TYPE_DEBUG_MSG)
578			return;//ok for now we don't skip messeges from object, so skip only debug messages
579	}
580	*/
581
582	LLNearbyChatScreenChannel* channel = dynamic_cast<LLNearbyChatScreenChannel*>(mChannel);
583
584	if(channel)
585	{
586		// Handle IRC styled messages.
587		std::string toast_msg;
588		if (chat_msg.mChatStyle == CHAT_STYLE_IRC)
589		{
590			if (!chat_msg.mFromName.empty())
591			{
592				toast_msg += chat_msg.mFromName;
593			}
594			toast_msg += chat_msg.mText.substr(3);
595		}
596		else
597		{
598			toast_msg = chat_msg.mText;
599		}
600
601		// Add a nearby chat toast.
602		LLUUID id;
603		id.generate();
604		notification["id"] = id;
605		std::string r_color_name = "White";
606		F32 r_color_alpha = 1.0f; 
607		LLViewerChat::getChatColor( chat_msg, r_color_name, r_color_alpha);
608		
609		notification["text_color"] = r_color_name;
610		notification["color_alpha"] = r_color_alpha;
611		notification["font_size"] = (S32)LLViewerChat::getChatFontSize() ;
612		notification["message"] = toast_msg;
613		channel->addNotification(notification);	
614	}
615}
616
617void LLNearbyChatHandler::onDeleteToast(LLToast* toast)
618{
619}
620
621
622//-----------------------------------------------------------------------------------------------
623// LLNearbyChatToast
624//-----------------------------------------------------------------------------------------------
625
626// virtual
627void LLNearbyChatToast::onClose(bool app_quitting)
628{
629	mNearbyChatScreenChannelp->onToastDestroyed(this, app_quitting);
630}
631
632// EOF