/** * @file llchatitemscontainer.cpp * @brief chat history scrolling panel implementation * * $LicenseInfo:firstyear=2009&license=viewergpl$ * * Copyright (c) 2009, Linden Research, Inc. * * Second Life Viewer Source Code * The source code in this file ("Source Code") is provided by Linden Lab * to you under the terms of the GNU General Public License, version 2.0 * ("GPL"), unless you have obtained a separate licensing agreement * ("Other License"), formally executed by you and Linden Lab. Terms of * the GPL can be found in doc/GPL-license.txt in this distribution, or * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 * * There are special exceptions to the terms and conditions of the GPL as * it is applied to this Source Code. View the full text of the exception * in the file doc/FLOSS-exception.txt in this software distribution, or * online at * http://secondlifegrid.net/programs/open_source/licensing/flossexception * * By copying, modifying or distributing this software, you acknowledge * that you have read and understood your obligations described above, * and agree to abide by those obligations. * * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, * COMPLETENESS OR PERFORMANCE. * $/LicenseInfo$ */ #include "llviewerprecompiledheaders.h" #include "llchatitemscontainerctrl.h" #include "lltextbox.h" #include "llchatmsgbox.h" #include "llavatariconctrl.h" #include "llfloaterreg.h" #include "lllocalcliprect.h" #include "lltrans.h" #include "llviewercontrol.h" #include "llagentdata.h" static const S32 BORDER_MARGIN = 2; static const S32 PARENT_BORDER_MARGIN = 0; static const S32 HORIZONTAL_MULTIPLE = 8; static const S32 VERTICAL_MULTIPLE = 16; static const F32 MIN_AUTO_SCROLL_RATE = 120.f; static const F32 MAX_AUTO_SCROLL_RATE = 500.f; static const F32 AUTO_SCROLL_RATE_ACCEL = 120.f; static const S32 msg_left_offset = 30; static const S32 msg_right_offset = 10; #define MAX_CHAT_HISTORY 100 static LLDefaultChildRegistry::Register t2("chat_items_container"); //******************************************************************************************************************* //LLChatItemCtrl //******************************************************************************************************************* LLChatItemCtrl* LLChatItemCtrl::createInstance() { LLChatItemCtrl* item = new LLChatItemCtrl(); LLUICtrlFactory::getInstance()->buildPanel(item, "panel_chat_item.xml"); return item; } void LLChatItemCtrl::draw() { LLPanel::draw(); } void LLChatItemCtrl::reshape (S32 width, S32 height, BOOL called_from_parent ) { LLPanel::reshape(width, height,called_from_parent); // *NOTE: we must check if child items exist because reshape is called from the // LLView::initFromParams BEFORE postBuild is called and child controls are not exist yet LLPanel* caption = findChild("msg_caption", false); LLChatMsgBox* msg_text = findChild("msg_text" ,false); if(caption && msg_text) { LLRect caption_rect = caption->getRect(); caption_rect.setLeftTopAndSize( 2, height, width - 4, caption_rect.getHeight()); caption->reshape( width - 4, caption_rect.getHeight(), 1); caption->setRect(caption_rect); LLRect msg_text_rect = msg_text->getRect(); msg_text_rect.setLeftTopAndSize( msg_left_offset, height - caption_rect.getHeight() , width - msg_left_offset - msg_right_offset, height - caption_rect.getHeight()); msg_text->reshape( width - msg_left_offset - msg_right_offset, height - caption_rect.getHeight(), 1); msg_text->setRect(msg_text_rect); } } BOOL LLChatItemCtrl::postBuild() { return LLPanel::postBuild(); } std::string LLChatItemCtrl::appendTime() { time_t utc_time; utc_time = time_corrected(); std::string timeStr ="["+ LLTrans::getString("TimeHour")+"]:[" +LLTrans::getString("TimeMin")+"] "; LLSD substitution; substitution["datetime"] = (S32) utc_time; LLStringUtil::format (timeStr, substitution); return timeStr; } void LLChatItemCtrl::addText (const std::string& message) { LLChatMsgBox* msg_text = getChild("msg_text", false); msg_text->addText(message); mMessages.push_back(message); } void LLChatItemCtrl::setMessage (const LLChat& msg) { LLPanel* caption = getChild("msg_caption", false); std::string str_sender; if(gAgentID != msg.mFromID) str_sender = msg.mFromName; else str_sender = LLTrans::getString("You");; caption->getChild("sender_name", false)->setText(str_sender); std::string tt = appendTime(); caption->getChild("msg_time", false)->setText(tt); caption->getChild("avatar_icon", false)->setValue(msg.mFromID); mOriginalMessage = msg; LLChatMsgBox* msg_text = getChild("msg_text", false); msg_text->setText(msg.mText); LLUICtrl* msg_inspector = caption->getChild("msg_inspector"); if(mOriginalMessage.mSourceType != CHAT_SOURCE_AGENT) msg_inspector->setVisible(false); mMessages.clear(); } void LLChatItemCtrl::snapToMessageHeight () { LLChatMsgBox* text_box = getChild("msg_text", false); S32 new_height = text_box->getTextPixelHeight(); LLRect panel_rect = getRect(); S32 caption_height = 0; LLPanel* caption = getChild("msg_caption", false); caption_height = caption->getRect().getHeight(); panel_rect.setLeftTopAndSize( panel_rect.mLeft, panel_rect.mTop, panel_rect.getWidth() , caption_height + new_height); reshape( getRect().getWidth(), caption_height + new_height, 1); setRect(panel_rect); } void LLChatItemCtrl::setWidth(S32 width) { LLChatMsgBox* text_box = getChild("msg_text", false); text_box->reshape(width - msg_left_offset - msg_right_offset,100/*its not magic number, we just need any number*/); LLChatMsgBox* msg_text = getChild("msg_text", false); if(mOriginalMessage.mText.length()) msg_text->setText(mOriginalMessage.mText); for(size_t i=0;iaddText(mMessages[i]); setRect(LLRect(getRect().mLeft, getRect().mTop, getRect().mLeft + width , getRect().mBottom)); snapToMessageHeight (); } void LLChatItemCtrl::onMouseLeave (S32 x, S32 y, MASK mask) { LLPanel* caption = getChild("msg_caption", false); LLUICtrl* msg_inspector = caption->getChild("msg_inspector"); msg_inspector->setVisible(false); } void LLChatItemCtrl::onMouseEnter (S32 x, S32 y, MASK mask) { if(mOriginalMessage.mSourceType != CHAT_SOURCE_AGENT) return; LLPanel* caption = getChild("msg_caption", false); LLUICtrl* msg_inspector = caption->getChild("msg_inspector"); msg_inspector->setVisible(true); } BOOL LLChatItemCtrl::handleMouseDown (S32 x, S32 y, MASK mask) { if(mOriginalMessage.mSourceType != CHAT_SOURCE_AGENT) return LLPanel::handleMouseDown(x,y,mask); LLPanel* caption = getChild("msg_caption", false); LLUICtrl* msg_inspector = caption->getChild("msg_inspector"); S32 local_x = x - msg_inspector->getRect().mLeft - caption->getRect().mLeft; S32 local_y = y - msg_inspector->getRect().mBottom - caption->getRect().mBottom; if(msg_inspector->pointInView(local_x, local_y)) { LLFloaterReg::showInstance("inspect_avatar", mOriginalMessage.mFromID); } return LLPanel::handleMouseDown(x,y,mask); } void LLChatItemCtrl::setHeaderVisibility(EShowItemHeader e) { LLPanel* caption = getChild("msg_caption", false); LLUICtrl* icon = caption->getChild("avatar_icon", false); LLUICtrl* name = caption->getChild("sender_name", false); icon->setVisible(e == CHATITEMHEADER_SHOW_ONLY_ICON || e==CHATITEMHEADER_SHOW_BOTH); name->setVisible(e == CHATITEMHEADER_SHOW_ONLY_NAME || e==CHATITEMHEADER_SHOW_BOTH); } bool LLChatItemCtrl::canAddText () { LLChatMsgBox* msg_text = findChild("msg_text"); if(!msg_text) return false; return msg_text->getTextLinesNum()<10; } BOOL LLChatItemCtrl::handleRightMouseDown(S32 x, S32 y, MASK mask) { LLPanel* caption = getChild("msg_caption", false); LLUICtrl* avatar_icon = caption->getChild("avatar_icon", false); S32 local_x = x - avatar_icon->getRect().mLeft - caption->getRect().mLeft; S32 local_y = y - avatar_icon->getRect().mBottom - caption->getRect().mBottom; //eat message for avatar icon if msg was from object if(avatar_icon->pointInView(local_x, local_y) && mOriginalMessage.mSourceType != CHAT_SOURCE_AGENT) return TRUE; return LLPanel::handleRightMouseDown(x,y,mask); } //******************************************************************************************************************* //LLChatItemsContainerCtrl //******************************************************************************************************************* LLChatItemsContainerCtrl::LLChatItemsContainerCtrl(const Params& params):LLPanel(params) { mEShowItemHeader = CHATITEMHEADER_SHOW_BOTH; } void LLChatItemsContainerCtrl::addMessage(const LLChat& msg) { /* if(msg.mChatType == CHAT_TYPE_DEBUG_MSG) return; */ if(mItems.size() >= MAX_CHAT_HISTORY) { LLChatItemCtrl* item = mItems[0]; removeChild(item); delete item; mItems.erase(mItems.begin()); } if(mItems.size() > 0 && msg.mFromID == mItems[mItems.size()-1]->getMessage().mFromID && (msg.mTime-mItems[mItems.size()-1]->getMessage().mTime)<60 && mItems[mItems.size()-1]->canAddText() ) { mItems[mItems.size()-1]->addText(msg.mText); mItems[mItems.size()-1]->snapToMessageHeight(); } else { LLChatItemCtrl* item = LLChatItemCtrl::createInstance(); mItems.push_back(item); addChild(item,0); item->setWidth(getRect().getWidth() - 16); item->setMessage(msg); item->snapToMessageHeight(); item->setHeaderVisibility((EShowItemHeader)gSavedSettings.getS32("nearbychat_showicons_and_names")); item->setVisible(true); } arrange(getRect().getWidth(),getRect().getHeight()); updateLayout(getRect().getWidth(),getRect().getHeight()); scrollToBottom(); } void LLChatItemsContainerCtrl::scrollToBottom () { if(mScrollbar->getVisible()) { mScrollbar->setDocPos(mScrollbar->getDocPosMax()); onScrollPosChangeCallback(0,0); } } void LLChatItemsContainerCtrl::draw() { LLLocalClipRect clip(getRect()); LLPanel::draw(); } void LLChatItemsContainerCtrl::reshape (S32 width, S32 height, BOOL called_from_parent ) { S32 delta_width = width - getRect().getWidth(); S32 delta_height = height - getRect().getHeight(); if (delta_width || delta_height || sForceReshape) { arrange(width, height); } updateBoundingRect(); } void LLChatItemsContainerCtrl::arrange (S32 width, S32 height) { S32 delta_width = width - getRect().getWidth(); if(delta_width)//width changed...too bad. now we need to reformat all items reformatHistoryScrollItems(width); calcRecuiredHeight(); show_hide_scrollbar(width,height); updateLayout(width,height); } void LLChatItemsContainerCtrl::reformatHistoryScrollItems(S32 width) { for(std::vector::iterator it = mItems.begin(); it != mItems.end();++it) { (*it)->setWidth(width); } } S32 LLChatItemsContainerCtrl::calcRecuiredHeight () { S32 rec_height = 0; std::vector::iterator it; for(it=mItems.begin(); it!=mItems.end(); ++it) { rec_height += (*it)->getRect().getHeight(); } mInnerRect.setLeftTopAndSize(0,rec_height + BORDER_MARGIN*2,getRect().getWidth(),rec_height + BORDER_MARGIN); return mInnerRect.getHeight(); } void LLChatItemsContainerCtrl::updateLayout (S32 width, S32 height) { S32 panel_top = height - BORDER_MARGIN ; S32 panel_width = width; if(mScrollbar->getVisible()) { static LLUICachedControl scrollbar_size ("UIScrollbarSize", 0); panel_top+=mScrollbar->getDocPos(); panel_width-=scrollbar_size; } //set sizes for first panels and dragbars for(size_t i=0;igetRect(); panelSetLeftTopAndSize(mItems[i],panel_rect.mLeft,panel_top,panel_width,panel_rect.getHeight()); panel_top-=panel_rect.getHeight(); } } void LLChatItemsContainerCtrl::show_hide_scrollbar (S32 width, S32 height) { calcRecuiredHeight(); if(getRecuiredHeight() > height ) showScrollbar(width, height); else hideScrollbar(width, height); } void LLChatItemsContainerCtrl::showScrollbar (S32 width, S32 height) { bool was_visible = mScrollbar->getVisible(); mScrollbar->setVisible(true); static LLUICachedControl scrollbar_size ("UIScrollbarSize", 0); panelSetLeftTopAndSize(mScrollbar,width-scrollbar_size ,height-PARENT_BORDER_MARGIN,scrollbar_size,height-2*PARENT_BORDER_MARGIN); mScrollbar->setPageSize(height); mScrollbar->setDocParams(mInnerRect.getHeight(),mScrollbar->getDocPos()); if(was_visible) { S32 scroll_pos = llmin(mScrollbar->getDocPos(), getRecuiredHeight() - height - 1); mScrollbar->setDocPos(scroll_pos); updateLayout(width,height); return; } } void LLChatItemsContainerCtrl::hideScrollbar (S32 width, S32 height) { if(mScrollbar->getVisible() == false) return; mScrollbar->setVisible(false); mScrollbar->setDocPos(0); if(mItems.size()>0) { S32 panel_top = height - BORDER_MARGIN; // Top coordinate of the first panel S32 diff = panel_top - mItems[0]->getRect().mTop; shiftPanels(diff); } } //--------------------------------------------------------------------------------- void LLChatItemsContainerCtrl::panelSetLeftTopAndSize(LLView* panel, S32 left, S32 top, S32 width, S32 height) { if(!panel) return; LLRect panel_rect = panel->getRect(); panel_rect.setLeftTopAndSize( left, top, width, height); panel->reshape( width, height, 1); panel->setRect(panel_rect); } void LLChatItemsContainerCtrl::panelShiftVertical(LLView* panel,S32 delta) { if(!panel) return; panel->translate(0,delta); } void LLChatItemsContainerCtrl::shiftPanels(S32 delta) { //Arrange panels for(std::vector::iterator it = mItems.begin(); it != mItems.end();++it) { panelShiftVertical((*it),delta); } } //--------------------------------------------------------------------------------- void LLChatItemsContainerCtrl::onScrollPosChangeCallback(S32, LLScrollbar*) { updateLayout(getRect().getWidth(),getRect().getHeight()); } BOOL LLChatItemsContainerCtrl::postBuild() { static LLUICachedControl scrollbar_size ("UIScrollbarSize", 0); LLRect scroll_rect; scroll_rect.setOriginAndSize( getRect().getWidth() - scrollbar_size, 1, scrollbar_size, getRect().getHeight() - 1); LLScrollbar::Params sbparams; sbparams.name("scrollable vertical"); sbparams.rect(scroll_rect); sbparams.orientation(LLScrollbar::VERTICAL); sbparams.doc_size(mInnerRect.getHeight()); sbparams.doc_pos(0); sbparams.page_size(mInnerRect.getHeight()); sbparams.step_size(VERTICAL_MULTIPLE); sbparams.follows.flags(FOLLOWS_RIGHT | FOLLOWS_TOP | FOLLOWS_BOTTOM); sbparams.change_callback(boost::bind(&LLChatItemsContainerCtrl::onScrollPosChangeCallback, this, _1, _2)); mScrollbar = LLUICtrlFactory::create (sbparams); LLView::addChild( mScrollbar ); mScrollbar->setVisible( true ); mScrollbar->setFollowsRight(); mScrollbar->setFollowsTop(); mScrollbar->setFollowsBottom(); reformatHistoryScrollItems(getRect().getWidth()); arrange(getRect().getWidth(),getRect().getHeight()); return LLPanel::postBuild(); } BOOL LLChatItemsContainerCtrl::handleMouseDown (S32 x, S32 y, MASK mask) { return LLPanel::handleMouseDown(x,y,mask); } BOOL LLChatItemsContainerCtrl::handleKeyHere (KEY key, MASK mask) { if( mScrollbar->getVisible() && mScrollbar->handleKeyHere( key,mask ) ) return TRUE; return LLPanel::handleKeyHere(key,mask); } BOOL LLChatItemsContainerCtrl::handleScrollWheel ( S32 x, S32 y, S32 clicks ) { if( mScrollbar->getVisible() && mScrollbar->handleScrollWheel( 0, 0, clicks ) ) return TRUE; return false; } void LLChatItemsContainerCtrl::setHeaderVisibility(EShowItemHeader e) { if(e == mEShowItemHeader) return; mEShowItemHeader = e; for(std::vector::iterator it = mItems.begin(); it != mItems.end();++it) { (*it)->setHeaderVisibility(e); } }