PageRenderTime 166ms CodeModel.GetById 15ms app.highlight 134ms RepoModel.GetById 1ms app.codeStats 1ms

/indra/newview/llchathistory.cpp

https://bitbucket.org/lindenlab/viewer-beta/
C++ | 967 lines | 744 code | 144 blank | 79 comment | 167 complexity | 35946e35f7bd1f6b345d05172c530fb9 MD5 | raw file
  1/** 
  2 * @file llchathistory.cpp
  3 * @brief LLTextEditor base class
  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 "llchathistory.h"
 30
 31#include "llavatarnamecache.h"
 32#include "llinstantmessage.h"
 33
 34#include "llimview.h"
 35#include "llcommandhandler.h"
 36#include "llpanel.h"
 37#include "lluictrlfactory.h"
 38#include "llscrollcontainer.h"
 39#include "llavatariconctrl.h"
 40#include "llcallingcard.h" //for LLAvatarTracker
 41#include "llagentdata.h"
 42#include "llavataractions.h"
 43#include "lltrans.h"
 44#include "llfloaterreg.h"
 45#include "llfloatersidepanelcontainer.h"
 46#include "llmutelist.h"
 47#include "llstylemap.h"
 48#include "llslurl.h"
 49#include "lllayoutstack.h"
 50#include "llagent.h"
 51#include "llnotificationsutil.h"
 52#include "lltoastnotifypanel.h"
 53#include "lltooltip.h"
 54#include "llviewerregion.h"
 55#include "llviewertexteditor.h"
 56#include "llworld.h"
 57#include "lluiconstants.h"
 58#include "llstring.h"
 59
 60#include "llviewercontrol.h"
 61
 62static LLDefaultChildRegistry::Register<LLChatHistory> r("chat_history");
 63
 64const static std::string NEW_LINE(rawstr_to_utf8("\n"));
 65
 66const static std::string SLURL_APP_AGENT = "secondlife:///app/agent/";
 67const static std::string SLURL_ABOUT = "/about";
 68
 69// support for secondlife:///app/objectim/{UUID}/ SLapps
 70class LLObjectIMHandler : public LLCommandHandler
 71{
 72public:
 73	// requests will be throttled from a non-trusted browser
 74	LLObjectIMHandler() : LLCommandHandler("objectim", UNTRUSTED_THROTTLE) {}
 75
 76	bool handle(const LLSD& params, const LLSD& query_map, LLMediaCtrl* web)
 77	{
 78		if (params.size() < 1)
 79		{
 80			return false;
 81		}
 82
 83		LLUUID object_id;
 84		if (!object_id.set(params[0], FALSE))
 85		{
 86			return false;
 87		}
 88
 89		LLSD payload;
 90		payload["object_id"] = object_id;
 91		payload["owner_id"] = query_map["owner"];
 92		payload["name"] = query_map["name"];
 93		payload["slurl"] = LLWeb::escapeURL(query_map["slurl"]);
 94		payload["group_owned"] = query_map["groupowned"];
 95		LLFloaterReg::showInstance("inspect_remote_object", payload);
 96		return true;
 97	}
 98};
 99LLObjectIMHandler gObjectIMHandler;
100
101class LLChatHistoryHeader: public LLPanel
102{
103public:
104	LLChatHistoryHeader()
105	:	LLPanel(),
106		mPopupMenuHandleAvatar(),
107		mPopupMenuHandleObject(),
108		mAvatarID(),
109		mSourceType(CHAT_SOURCE_UNKNOWN),
110		mFrom(),
111		mSessionID(),
112		mMinUserNameWidth(0),
113		mUserNameFont(NULL)
114	{}
115
116	static LLChatHistoryHeader* createInstance(const std::string& file_name)
117	{
118		LLChatHistoryHeader* pInstance = new LLChatHistoryHeader;
119		pInstance->buildFromFile(file_name);	
120		return pInstance;
121	}
122
123	~LLChatHistoryHeader()
124	{
125		// Detach the info button so that it doesn't get destroyed (EXT-8463).
126		hideInfoCtrl();
127	}
128
129	BOOL handleMouseUp(S32 x, S32 y, MASK mask)
130	{
131		return LLPanel::handleMouseUp(x,y,mask);
132	}
133
134	void onObjectIconContextMenuItemClicked(const LLSD& userdata)
135	{
136		std::string level = userdata.asString();
137
138		if (level == "profile")
139		{
140			LLFloaterReg::showInstance("inspect_remote_object", mObjectData);
141		}
142		else if (level == "block")
143		{
144			LLMuteList::getInstance()->add(LLMute(getAvatarId(), mFrom, LLMute::OBJECT));
145
146			LLFloaterSidePanelContainer::showPanel("people", "panel_block_list_sidetray", LLSD().with("blocked_to_select", getAvatarId()));
147		}
148	}
149
150	void onAvatarIconContextMenuItemClicked(const LLSD& userdata)
151	{
152		std::string level = userdata.asString();
153
154		if (level == "profile")
155		{
156			LLAvatarActions::showProfile(getAvatarId());
157		}
158		else if (level == "im")
159		{
160			LLAvatarActions::startIM(getAvatarId());
161		}
162		else if (level == "add")
163		{
164			LLAvatarActions::requestFriendshipDialog(getAvatarId(), mFrom);
165		}
166		else if (level == "remove")
167		{
168			LLAvatarActions::removeFriendDialog(getAvatarId());
169		}
170	}
171
172	BOOL postBuild()
173	{
174		LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar;
175
176		registrar.add("AvatarIcon.Action", boost::bind(&LLChatHistoryHeader::onAvatarIconContextMenuItemClicked, this, _2));
177		registrar.add("ObjectIcon.Action", boost::bind(&LLChatHistoryHeader::onObjectIconContextMenuItemClicked, this, _2));
178
179		LLMenuGL* menu = LLUICtrlFactory::getInstance()->createFromFile<LLMenuGL>("menu_avatar_icon.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance());
180		mPopupMenuHandleAvatar = menu->getHandle();
181
182		menu = LLUICtrlFactory::getInstance()->createFromFile<LLMenuGL>("menu_object_icon.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance());
183		mPopupMenuHandleObject = menu->getHandle();
184
185		setDoubleClickCallback(boost::bind(&LLChatHistoryHeader::showInspector, this));
186
187		setMouseEnterCallback(boost::bind(&LLChatHistoryHeader::showInfoCtrl, this));
188		setMouseLeaveCallback(boost::bind(&LLChatHistoryHeader::hideInfoCtrl, this));
189
190		return LLPanel::postBuild();
191	}
192
193	bool pointInChild(const std::string& name,S32 x,S32 y)
194	{
195		LLUICtrl* child = findChild<LLUICtrl>(name);
196		if(!child)
197			return false;
198		
199		LLView* parent = child->getParent();
200		if(parent!=this)
201		{
202			x-=parent->getRect().mLeft;
203			y-=parent->getRect().mBottom;
204		}
205
206		S32 local_x = x - child->getRect().mLeft ;
207		S32 local_y = y - child->getRect().mBottom ;
208		return 	child->pointInView(local_x, local_y);
209	}
210
211	BOOL handleRightMouseDown(S32 x, S32 y, MASK mask)
212	{
213		if(pointInChild("avatar_icon",x,y) || pointInChild("user_name",x,y))
214		{
215			showContextMenu(x,y);
216			return TRUE;
217		}
218
219		return LLPanel::handleRightMouseDown(x,y,mask);
220	}
221
222	void showInspector()
223	{
224		if (mAvatarID.isNull() && CHAT_SOURCE_SYSTEM != mSourceType) return;
225		
226		if (mSourceType == CHAT_SOURCE_OBJECT)
227		{
228			LLFloaterReg::showInstance("inspect_remote_object", mObjectData);
229		}
230		else if (mSourceType == CHAT_SOURCE_AGENT)
231		{
232			LLFloaterReg::showInstance("inspect_avatar", LLSD().with("avatar_id", mAvatarID));
233		}
234		//if chat source is system, you may add "else" here to define behaviour.
235	}
236
237	static void onClickInfoCtrl(LLUICtrl* info_ctrl)
238	{
239		if (!info_ctrl) return;
240
241		LLChatHistoryHeader* header = dynamic_cast<LLChatHistoryHeader*>(info_ctrl->getParent());	
242		if (!header) return;
243
244		header->showInspector();
245	}
246
247
248	const LLUUID&		getAvatarId () const { return mAvatarID;}
249
250	void setup(const LLChat& chat, const LLStyle::Params& style_params, const LLSD& args)
251	{
252		mAvatarID = chat.mFromID;
253		mSessionID = chat.mSessionID;
254		mSourceType = chat.mSourceType;
255
256		//*TODO overly defensive thing, source type should be maintained out there
257		if((chat.mFromID.isNull() && chat.mFromName.empty()) || (chat.mFromName == SYSTEM_FROM && chat.mFromID.isNull()))
258		{
259			mSourceType = CHAT_SOURCE_SYSTEM;
260		}  
261
262		mUserNameFont = style_params.font();
263		LLTextBox* user_name = getChild<LLTextBox>("user_name");
264		user_name->setReadOnlyColor(style_params.readonly_color());
265		user_name->setColor(style_params.color());
266
267		if (chat.mFromName.empty()
268			|| mSourceType == CHAT_SOURCE_SYSTEM)
269		{
270			mFrom = LLTrans::getString("SECOND_LIFE");
271			user_name->setValue(mFrom);
272			updateMinUserNameWidth();
273		}
274		else if (mSourceType == CHAT_SOURCE_AGENT
275				 && !mAvatarID.isNull()
276				 && chat.mChatStyle != CHAT_STYLE_HISTORY)
277		{
278			// ...from a normal user, lookup the name and fill in later.
279			// *NOTE: Do not do this for chat history logs, otherwise the viewer
280			// will flood the People API with lookup requests on startup
281
282			// Start with blank so sample data from XUI XML doesn't
283			// flash on the screen
284			user_name->setValue( LLSD() );
285			LLAvatarNameCache::get(mAvatarID,
286				boost::bind(&LLChatHistoryHeader::onAvatarNameCache, this, _1, _2));
287		}
288		else if (chat.mChatStyle == CHAT_STYLE_HISTORY ||
289				 mSourceType == CHAT_SOURCE_AGENT)
290		{
291			//if it's an avatar name with a username add formatting
292			S32 username_start = chat.mFromName.rfind(" (");
293			S32 username_end = chat.mFromName.rfind(')');
294			
295			if (username_start != std::string::npos &&
296				username_end == (chat.mFromName.length() - 1))
297			{
298				mFrom = chat.mFromName.substr(0, username_start);
299				user_name->setValue(mFrom);
300
301				if (gSavedSettings.getBOOL("NameTagShowUsernames"))
302				{
303					std::string username = chat.mFromName.substr(username_start + 2);
304					username = username.substr(0, username.length() - 1);
305					LLStyle::Params style_params_name;
306					LLColor4 userNameColor = LLUIColorTable::instance().getColor("EmphasisColor");
307					style_params_name.color(userNameColor);
308					style_params_name.font.name("SansSerifSmall");
309					style_params_name.font.style("NORMAL");
310					style_params_name.readonly_color(userNameColor);
311					user_name->appendText("  - " + username, FALSE, style_params_name);
312				}
313			}
314			else
315			{
316				mFrom = chat.mFromName;
317				user_name->setValue(mFrom);
318				updateMinUserNameWidth();
319			}
320		}
321		else
322		{
323			// ...from an object, just use name as given
324			mFrom = chat.mFromName;
325			user_name->setValue(mFrom);
326			updateMinUserNameWidth();
327		}
328
329
330		setTimeField(chat);
331
332		// Set up the icon.
333		LLAvatarIconCtrl* icon = getChild<LLAvatarIconCtrl>("avatar_icon");
334
335		if(mSourceType != CHAT_SOURCE_AGENT ||	mAvatarID.isNull())
336			icon->setDrawTooltip(false);
337
338		switch (mSourceType)
339		{
340			case CHAT_SOURCE_AGENT:
341				icon->setValue(chat.mFromID);
342				break;
343			case CHAT_SOURCE_OBJECT:
344				icon->setValue(LLSD("OBJECT_Icon"));
345				break;
346			case CHAT_SOURCE_SYSTEM:
347				icon->setValue(LLSD("SL_Logo"));
348				break;
349			case CHAT_SOURCE_UNKNOWN: 
350				icon->setValue(LLSD("Unknown_Icon"));
351		}
352
353		// In case the message came from an object, save the object info
354		// to be able properly show its profile.
355		if ( chat.mSourceType == CHAT_SOURCE_OBJECT)
356		{
357			std::string slurl = args["slurl"].asString();
358			if (slurl.empty())
359			{
360				LLViewerRegion *region = LLWorld::getInstance()->getRegionFromPosAgent(chat.mPosAgent);
361				if(region)
362				{
363					LLSLURL region_slurl(region->getName(), chat.mPosAgent);
364					slurl = region_slurl.getLocationString();
365				}
366			}
367
368			LLSD payload;
369			payload["object_id"]	= chat.mFromID;
370			payload["name"]			= chat.mFromName;
371			payload["owner_id"]		= chat.mOwnerID;
372			payload["slurl"]		= LLWeb::escapeURL(slurl);
373
374			mObjectData = payload;
375		}
376	}
377
378	/*virtual*/ void draw()
379	{
380		LLTextBox* user_name = getChild<LLTextBox>("user_name");
381		LLTextBox* time_box = getChild<LLTextBox>("time_box");
382
383		LLRect user_name_rect = user_name->getRect();
384		S32 user_name_width = user_name_rect.getWidth();
385		S32 time_box_width = time_box->getRect().getWidth();
386
387		if (time_box->getVisible() && user_name_width <= mMinUserNameWidth)
388		{
389			time_box->setVisible(FALSE);
390
391			user_name_rect.mRight += time_box_width;
392			user_name->reshape(user_name_rect.getWidth(), user_name_rect.getHeight());
393			user_name->setRect(user_name_rect);
394		}
395
396		if (!time_box->getVisible() && user_name_width > mMinUserNameWidth + time_box_width)
397		{
398			user_name_rect.mRight -= time_box_width;
399			user_name->reshape(user_name_rect.getWidth(), user_name_rect.getHeight());
400			user_name->setRect(user_name_rect);
401
402			time_box->setVisible(TRUE);
403		}
404
405		LLPanel::draw();
406	}
407
408	void updateMinUserNameWidth()
409	{
410		if (mUserNameFont)
411		{
412			LLTextBox* user_name = getChild<LLTextBox>("user_name");
413			const LLWString& text = user_name->getWText();
414			mMinUserNameWidth = mUserNameFont->getWidth(text.c_str()) + PADDING;
415		}
416	}
417
418	void onAvatarNameCache(const LLUUID& agent_id, const LLAvatarName& av_name)
419	{
420		mFrom = av_name.mDisplayName;
421
422		LLTextBox* user_name = getChild<LLTextBox>("user_name");
423		user_name->setValue( LLSD(av_name.mDisplayName ) );
424		user_name->setToolTip( av_name.mUsername );
425
426		if (gSavedSettings.getBOOL("NameTagShowUsernames") && 
427			LLAvatarNameCache::useDisplayNames() &&
428			!av_name.mIsDisplayNameDefault)
429		{
430			LLStyle::Params style_params_name;
431			LLColor4 userNameColor = LLUIColorTable::instance().getColor("EmphasisColor");
432			style_params_name.color(userNameColor);
433			style_params_name.font.name("SansSerifSmall");
434			style_params_name.font.style("NORMAL");
435			style_params_name.readonly_color(userNameColor);
436			user_name->appendText("  - " + av_name.mUsername, FALSE, style_params_name);
437		}
438		setToolTip( av_name.mUsername );
439		// name might have changed, update width
440		updateMinUserNameWidth();
441	}
442
443protected:
444	static const S32 PADDING = 20;
445
446	void showContextMenu(S32 x,S32 y)
447	{
448		if(mSourceType == CHAT_SOURCE_SYSTEM)
449			showSystemContextMenu(x,y);
450		if(mAvatarID.notNull() && mSourceType == CHAT_SOURCE_AGENT)
451			showAvatarContextMenu(x,y);
452		if(mAvatarID.notNull() && mSourceType == CHAT_SOURCE_OBJECT && SYSTEM_FROM != mFrom)
453			showObjectContextMenu(x,y);
454	}
455
456	void showSystemContextMenu(S32 x,S32 y)
457	{
458	}
459	
460	void showObjectContextMenu(S32 x,S32 y)
461	{
462		LLMenuGL* menu = (LLMenuGL*)mPopupMenuHandleObject.get();
463		if(menu)
464			LLMenuGL::showPopup(this, menu, x, y);
465	}
466	
467	void showAvatarContextMenu(S32 x,S32 y)
468	{
469		LLMenuGL* menu = (LLMenuGL*)mPopupMenuHandleAvatar.get();
470
471		if(menu)
472		{
473			bool is_friend = LLAvatarTracker::instance().getBuddyInfo(mAvatarID) != NULL;
474			
475			menu->setItemEnabled("Add Friend", !is_friend);
476			menu->setItemEnabled("Remove Friend", is_friend);
477
478			if(gAgentID == mAvatarID)
479			{
480				menu->setItemEnabled("Add Friend", false);
481				menu->setItemEnabled("Send IM", false);
482				menu->setItemEnabled("Remove Friend", false);
483			}
484
485			if (mSessionID == LLIMMgr::computeSessionID(IM_NOTHING_SPECIAL, mAvatarID))
486			{
487				menu->setItemVisible("Send IM", false);
488			}
489
490			menu->buildDrawLabels();
491			menu->updateParent(LLMenuGL::sMenuContainer);
492			LLMenuGL::showPopup(this, menu, x, y);
493		}
494	}
495
496	void showInfoCtrl()
497	{
498		if (mAvatarID.isNull() || mFrom.empty() || SYSTEM_FROM == mFrom) return;
499				
500		if (!sInfoCtrl)
501		{
502			// *TODO: Delete the button at exit.
503			sInfoCtrl = LLUICtrlFactory::createFromFile<LLUICtrl>("inspector_info_ctrl.xml", NULL, LLPanel::child_registry_t::instance());
504			if (sInfoCtrl)
505			{
506				sInfoCtrl->setCommitCallback(boost::bind(&LLChatHistoryHeader::onClickInfoCtrl, sInfoCtrl));
507			}
508		}
509
510		if (!sInfoCtrl)
511		{
512			llassert(sInfoCtrl != NULL);
513			return;
514		}
515
516		LLTextBox* name = getChild<LLTextBox>("user_name");
517		LLRect sticky_rect = name->getRect();
518		S32 icon_x = llmin(sticky_rect.mLeft + name->getTextBoundingRect().getWidth() + 7, sticky_rect.mRight - 3);
519		sInfoCtrl->setOrigin(icon_x, sticky_rect.getCenterY() - sInfoCtrl->getRect().getHeight() / 2 ) ;
520		addChild(sInfoCtrl);
521	}
522
523	void hideInfoCtrl()
524	{
525		if (!sInfoCtrl) return;
526
527		if (sInfoCtrl->getParent() == this)
528		{
529			removeChild(sInfoCtrl);
530		}
531	}
532
533private:
534	void setTimeField(const LLChat& chat)
535	{
536		LLTextBox* time_box = getChild<LLTextBox>("time_box");
537
538		LLRect rect_before = time_box->getRect();
539
540		time_box->setValue(chat.mTimeStr);
541
542		// set necessary textbox width to fit all text
543		time_box->reshapeToFitText();
544		LLRect rect_after = time_box->getRect();
545
546		// move rect to the left to correct position...
547		S32 delta_pos_x = rect_before.getWidth() - rect_after.getWidth();
548		S32 delta_pos_y = rect_before.getHeight() - rect_after.getHeight();
549		time_box->translate(delta_pos_x, delta_pos_y);
550
551		//... & change width of the name control
552		LLView* user_name = getChild<LLView>("user_name");
553		const LLRect& user_rect = user_name->getRect();
554		user_name->reshape(user_rect.getWidth() + delta_pos_x, user_rect.getHeight());
555	}
556
557protected:
558	LLHandle<LLView>	mPopupMenuHandleAvatar;
559	LLHandle<LLView>	mPopupMenuHandleObject;
560
561	static LLUICtrl*	sInfoCtrl;
562
563	LLUUID			    mAvatarID;
564	LLSD				mObjectData;
565	EChatSourceType		mSourceType;
566	std::string			mFrom;
567	LLUUID				mSessionID;
568
569	S32					mMinUserNameWidth;
570	const LLFontGL*		mUserNameFont;
571};
572
573LLUICtrl* LLChatHistoryHeader::sInfoCtrl = NULL;
574
575LLChatHistory::LLChatHistory(const LLChatHistory::Params& p)
576:	LLUICtrl(p),
577	mMessageHeaderFilename(p.message_header),
578	mMessageSeparatorFilename(p.message_separator),
579	mLeftTextPad(p.left_text_pad),
580	mRightTextPad(p.right_text_pad),
581	mLeftWidgetPad(p.left_widget_pad),
582	mRightWidgetPad(p.right_widget_pad),
583	mTopSeparatorPad(p.top_separator_pad),
584	mBottomSeparatorPad(p.bottom_separator_pad),
585	mTopHeaderPad(p.top_header_pad),
586	mBottomHeaderPad(p.bottom_header_pad),
587	mIsLastMessageFromLog(false)
588{
589	LLTextEditor::Params editor_params(p);
590	editor_params.rect = getLocalRect();
591	editor_params.follows.flags = FOLLOWS_ALL;
592	editor_params.enabled = false; // read only
593	editor_params.show_context_menu = "true";
594	mEditor = LLUICtrlFactory::create<LLTextEditor>(editor_params, this);
595}
596
597LLChatHistory::~LLChatHistory()
598{
599	this->clear();
600}
601
602void LLChatHistory::initFromParams(const LLChatHistory::Params& p)
603{
604	static LLUICachedControl<S32> scrollbar_size ("UIScrollbarSize", 0);
605
606	LLRect stack_rect = getLocalRect();
607	stack_rect.mRight -= scrollbar_size;
608	LLLayoutStack::Params layout_p;
609	layout_p.rect = stack_rect;
610	layout_p.follows.flags = FOLLOWS_ALL;
611	layout_p.orientation = LLLayoutStack::VERTICAL;
612	layout_p.mouse_opaque = false;
613	
614	LLLayoutStack* stackp = LLUICtrlFactory::create<LLLayoutStack>(layout_p, this);
615	
616	const S32 NEW_TEXT_NOTICE_HEIGHT = 20;
617	
618	LLLayoutPanel::Params panel_p;
619	panel_p.name = "spacer";
620	panel_p.background_visible = false;
621	panel_p.has_border = false;
622	panel_p.mouse_opaque = false;
623	panel_p.min_dim = 30;
624	panel_p.auto_resize = true;
625	panel_p.user_resize = false;
626
627	stackp->addPanel(LLUICtrlFactory::create<LLLayoutPanel>(panel_p), LLLayoutStack::ANIMATE);
628
629	panel_p.name = "new_text_notice_holder";
630	LLRect new_text_notice_rect = getLocalRect();
631	new_text_notice_rect.mTop = new_text_notice_rect.mBottom + NEW_TEXT_NOTICE_HEIGHT;
632	panel_p.rect = new_text_notice_rect;
633	panel_p.background_opaque = true;
634	panel_p.background_visible = true;
635	panel_p.visible = false;
636	panel_p.min_dim = 0;
637	panel_p.auto_resize = false;
638	panel_p.user_resize = false;
639	mMoreChatPanel = LLUICtrlFactory::create<LLLayoutPanel>(panel_p);
640	
641	LLTextBox::Params text_p(p.more_chat_text);
642	text_p.rect = mMoreChatPanel->getLocalRect();
643	text_p.follows.flags = FOLLOWS_ALL;
644	text_p.name = "more_chat_text";
645	mMoreChatText = LLUICtrlFactory::create<LLTextBox>(text_p, mMoreChatPanel);
646	mMoreChatText->setClickedCallback(boost::bind(&LLChatHistory::onClickMoreText, this));
647
648	stackp->addPanel(mMoreChatPanel, LLLayoutStack::ANIMATE);
649}
650
651
652/*void LLChatHistory::updateTextRect()
653{
654	static LLUICachedControl<S32> texteditor_border ("UITextEditorBorder", 0);
655
656	LLRect old_text_rect = mVisibleTextRect;
657	mVisibleTextRect = mScroller->getContentWindowRect();
658	mVisibleTextRect.stretch(-texteditor_border);
659	mVisibleTextRect.mLeft += mLeftTextPad;
660	mVisibleTextRect.mRight -= mRightTextPad;
661	if (mVisibleTextRect != old_text_rect)
662	{
663		needsReflow();
664	}
665}*/
666
667LLView* LLChatHistory::getSeparator()
668{
669	LLPanel* separator = LLUICtrlFactory::getInstance()->createFromFile<LLPanel>(mMessageSeparatorFilename, NULL, LLPanel::child_registry_t::instance());
670	return separator;
671}
672
673LLView* LLChatHistory::getHeader(const LLChat& chat,const LLStyle::Params& style_params, const LLSD& args)
674{
675	LLChatHistoryHeader* header = LLChatHistoryHeader::createInstance(mMessageHeaderFilename);
676	header->setup(chat, style_params, args);
677	return header;
678}
679
680void LLChatHistory::onClickMoreText()
681{
682	mEditor->endOfDoc();
683}
684
685void LLChatHistory::clear()
686{
687	mLastFromName.clear();
688	mEditor->clear();
689	mLastFromID = LLUUID::null;
690}
691
692void LLChatHistory::appendMessage(const LLChat& chat, const LLSD &args, const LLStyle::Params& input_append_params)
693{
694	bool use_plain_text_chat_history = args["use_plain_text_chat_history"].asBoolean();
695
696	llassert(mEditor);
697	if (!mEditor)
698	{
699		return;
700	}
701
702	mEditor->setPlainText(use_plain_text_chat_history);
703
704	if (!mEditor->scrolledToEnd() && chat.mFromID != gAgent.getID() && !chat.mFromName.empty())
705	{
706		mUnreadChatSources.insert(chat.mFromName);
707		mMoreChatPanel->setVisible(TRUE);
708		std::string chatters;
709		for (unread_chat_source_t::iterator it = mUnreadChatSources.begin();
710			it != mUnreadChatSources.end();)
711		{
712			chatters += *it;
713			if (++it != mUnreadChatSources.end())
714			{
715				chatters += ", ";
716			}
717		}
718		LLStringUtil::format_map_t args;
719		args["SOURCES"] = chatters;
720
721		if (mUnreadChatSources.size() == 1)
722		{
723			mMoreChatText->setValue(LLTrans::getString("unread_chat_single", args));
724		}
725		else
726		{
727			mMoreChatText->setValue(LLTrans::getString("unread_chat_multiple", args));
728		}
729		S32 height = mMoreChatText->getTextPixelHeight() + 5;
730		mMoreChatPanel->reshape(mMoreChatPanel->getRect().getWidth(), height);
731	}
732
733	LLColor4 txt_color = LLUIColorTable::instance().getColor("White");
734	LLViewerChat::getChatColor(chat,txt_color);
735	LLFontGL* fontp = LLViewerChat::getChatFont();	
736	std::string font_name = LLFontGL::nameFromFont(fontp);
737	std::string font_size = LLFontGL::sizeFromFont(fontp);	
738	LLStyle::Params style_params;
739	style_params.color(txt_color);
740	style_params.readonly_color(txt_color);
741	style_params.font.name(font_name);
742	style_params.font.size(font_size);	
743	style_params.font.style(input_append_params.font.style);
744
745	std::string prefix = chat.mText.substr(0, 4);
746
747	//IRC styled /me messages.
748	bool irc_me = prefix == "/me " || prefix == "/me'";
749
750	// Delimiter after a name in header copy/past and in plain text mode
751	std::string delimiter = ": ";
752	std::string shout = LLTrans::getString("shout");
753	std::string whisper = LLTrans::getString("whisper");
754	if (chat.mChatType == CHAT_TYPE_SHOUT || 
755		chat.mChatType == CHAT_TYPE_WHISPER ||
756		chat.mText.compare(0, shout.length(), shout) == 0 ||
757		chat.mText.compare(0, whisper.length(), whisper) == 0)
758	{
759		delimiter = " ";
760	}
761
762	// Don't add any delimiter after name in irc styled messages
763	if (irc_me || chat.mChatStyle == CHAT_STYLE_IRC)
764	{
765		delimiter = LLStringUtil::null;
766		style_params.font.style = "ITALIC";
767	}
768
769	bool message_from_log = chat.mChatStyle == CHAT_STYLE_HISTORY;
770	// We graying out chat history by graying out messages that contains full date in a time string
771	if (message_from_log)
772	{
773		style_params.color(LLColor4::grey);
774		style_params.readonly_color(LLColor4::grey);
775	}
776
777	if (use_plain_text_chat_history)
778	{
779		LLStyle::Params timestamp_style(style_params);
780		if (!message_from_log)
781		{
782			LLColor4 timestamp_color = LLUIColorTable::instance().getColor("ChatTimestampColor");
783			timestamp_style.color(timestamp_color);
784			timestamp_style.readonly_color(timestamp_color);
785		}
786		mEditor->appendText("[" + chat.mTimeStr + "] ", mEditor->getText().size() != 0, timestamp_style);
787
788		if (utf8str_trim(chat.mFromName).size() != 0)
789		{
790			// Don't hotlink any messages from the system (e.g. "Second Life:"), so just add those in plain text.
791			if ( chat.mSourceType == CHAT_SOURCE_OBJECT && chat.mFromID.notNull())
792			{
793				// for object IMs, create a secondlife:///app/objectim SLapp
794				std::string url = LLViewerChat::getSenderSLURL(chat, args);
795
796				// set the link for the object name to be the objectim SLapp
797				// (don't let object names with hyperlinks override our objectim Url)
798				LLStyle::Params link_params(style_params);
799				LLColor4 link_color = LLUIColorTable::instance().getColor("HTMLLinkColor");
800				link_params.color = link_color;
801				link_params.readonly_color = link_color;
802				link_params.is_link = true;
803				link_params.link_href = url;
804
805				mEditor->appendText(chat.mFromName + delimiter,
806									false, link_params);
807			}
808			else if ( chat.mFromName != SYSTEM_FROM && chat.mFromID.notNull() && !message_from_log)
809			{
810				LLStyle::Params link_params(style_params);
811				link_params.overwriteFrom(LLStyleMap::instance().lookupAgent(chat.mFromID));
812
813				// Add link to avatar's inspector and delimiter to message.
814				mEditor->appendText(std::string(link_params.link_href) + delimiter, false, link_params);
815			}
816			else
817			{
818				mEditor->appendText("<nolink>" + chat.mFromName + "</nolink>" + delimiter, false, style_params);
819			}
820		}
821	}
822	else
823	{
824		LLView* view = NULL;
825		LLInlineViewSegment::Params p;
826		p.force_newline = true;
827		p.left_pad = mLeftWidgetPad;
828		p.right_pad = mRightWidgetPad;
829
830		LLDate new_message_time = LLDate::now();
831
832		if (mLastFromName == chat.mFromName 
833			&& mLastFromID == chat.mFromID
834			&& mLastMessageTime.notNull() 
835			&& (new_message_time.secondsSinceEpoch() - mLastMessageTime.secondsSinceEpoch()) < 60.0
836			&& mIsLastMessageFromLog == message_from_log)  //distinguish between current and previous chat session's histories
837		{
838			view = getSeparator();
839			p.top_pad = mTopSeparatorPad;
840			p.bottom_pad = mBottomSeparatorPad;
841		}
842		else
843		{
844			view = getHeader(chat, style_params, args);
845			if (mEditor->getText().size() == 0)
846				p.top_pad = 0;
847			else
848				p.top_pad = mTopHeaderPad;
849			p.bottom_pad = mBottomHeaderPad;
850			
851		}
852		p.view = view;
853
854		//Prepare the rect for the view
855		LLRect target_rect = mEditor->getDocumentView()->getRect();
856		// squeeze down the widget by subtracting padding off left and right
857		target_rect.mLeft += mLeftWidgetPad + mEditor->getHPad();
858		target_rect.mRight -= mRightWidgetPad;
859		view->reshape(target_rect.getWidth(), view->getRect().getHeight());
860		view->setOrigin(target_rect.mLeft, view->getRect().mBottom);
861
862		std::string widget_associated_text = "\n[" + chat.mTimeStr + "] ";
863		if (utf8str_trim(chat.mFromName).size() != 0 && chat.mFromName != SYSTEM_FROM)
864			widget_associated_text += chat.mFromName + delimiter;
865
866		mEditor->appendWidget(p, widget_associated_text, false);
867		mLastFromName = chat.mFromName;
868		mLastFromID = chat.mFromID;
869		mLastMessageTime = new_message_time;
870		mIsLastMessageFromLog = message_from_log;
871	}
872
873	if (chat.mNotifId.notNull())
874	{
875		LLNotificationPtr notification = LLNotificationsUtil::find(chat.mNotifId);
876		if (notification != NULL)
877		{
878			LLIMToastNotifyPanel* notify_box = new LLIMToastNotifyPanel(
879					notification, chat.mSessionID, LLRect::null, !use_plain_text_chat_history);
880			//we can't set follows in xml since it broke toasts behavior
881			notify_box->setFollowsLeft();
882			notify_box->setFollowsRight();
883			notify_box->setFollowsTop();
884
885			ctrl_list_t ctrls = notify_box->getControlPanel()->getCtrlList();
886			S32 offset = 0;
887			// Children were added by addChild() which uses push_front to insert them into list,
888			// so to get buttons in correct order reverse iterator is used (EXT-5906) 
889			for (ctrl_list_t::reverse_iterator it = ctrls.rbegin(); it != ctrls.rend(); it++)
890			{
891				LLButton * button = dynamic_cast<LLButton*> (*it);
892				if (button != NULL)
893				{
894					button->setOrigin( offset,
895							button->getRect().mBottom);
896					button->setLeftHPad(2 * HPAD);
897					button->setRightHPad(2 * HPAD);
898					// set zero width before perform autoResize()
899					button->setRect(LLRect(button->getRect().mLeft,
900							button->getRect().mTop, button->getRect().mLeft,
901							button->getRect().mBottom));
902					button->setAutoResize(true);
903					button->autoResize();
904					offset += HPAD + button->getRect().getWidth();
905					button->setFollowsNone();
906				}
907			}
908
909			//Prepare the rect for the view
910			LLRect target_rect = mEditor->getDocumentView()->getRect();
911			// squeeze down the widget by subtracting padding off left and right
912			target_rect.mLeft += mLeftWidgetPad + mEditor->getHPad();
913			target_rect.mRight -= mRightWidgetPad;
914			notify_box->reshape(target_rect.getWidth(),	notify_box->getRect().getHeight());
915			notify_box->setOrigin(target_rect.mLeft, notify_box->getRect().mBottom);
916
917			LLInlineViewSegment::Params params;
918			params.view = notify_box;
919			params.left_pad = mLeftWidgetPad;
920			params.right_pad = mRightWidgetPad;
921			mEditor->appendWidget(params, "\n", false);
922		}
923	}
924	else
925	{
926		std::string message = irc_me ? chat.mText.substr(3) : chat.mText;
927
928
929		//MESSAGE TEXT PROCESSING
930		//*HACK getting rid of redundant sender names in system notifications sent using sender name (see EXT-5010)
931		if (use_plain_text_chat_history && gAgentID != chat.mFromID && chat.mFromID.notNull())
932		{
933			std::string slurl_about = SLURL_APP_AGENT + chat.mFromID.asString() + SLURL_ABOUT;
934			if (message.length() > slurl_about.length() && 
935				message.compare(0, slurl_about.length(), slurl_about) == 0)
936			{
937				message = message.substr(slurl_about.length(), message.length()-1);
938			}
939		}
940
941		if (irc_me && !use_plain_text_chat_history)
942		{
943			message = chat.mFromName + message;
944		}
945		
946		mEditor->appendText(message, FALSE, style_params);
947	}
948
949	mEditor->blockUndo();
950
951	// automatically scroll to end when receiving chat from myself
952	if (chat.mFromID == gAgentID)
953	{
954		mEditor->setCursorAndScrollToEnd();
955	}
956}
957
958void LLChatHistory::draw()
959{
960	if (mEditor->scrolledToEnd())
961	{
962		mUnreadChatSources.clear();
963		mMoreChatPanel->setVisible(FALSE);
964	}
965
966	LLUICtrl::draw();
967}