PageRenderTime 147ms CodeModel.GetById 14ms app.highlight 118ms RepoModel.GetById 1ms app.codeStats 1ms

/indra/llui/llscrolllistctrl.cpp

https://bitbucket.org/lindenlab/viewer-beta/
C++ | 2776 lines | 2195 code | 388 blank | 193 comment | 428 complexity | b2b54eb8e0b2abb939898d90e0081bcf MD5 | raw file

Large files files are truncated, but you can click here to view the full file

   1 /** 
   2 * @file llscrolllistctrl.cpp
   3 * @brief Scroll lists are composed of rows (items), each of which 
   4 * contains columns (cells).
   5 *
   6 * $LicenseInfo:firstyear=2001&license=viewerlgpl$
   7 * Second Life Viewer Source Code
   8 * Copyright (C) 2010, Linden Research, Inc.
   9 * 
  10 * This library is free software; you can redistribute it and/or
  11 * modify it under the terms of the GNU Lesser General Public
  12 * License as published by the Free Software Foundation;
  13 * version 2.1 of the License only.
  14 * 
  15 * This library is distributed in the hope that it will be useful,
  16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
  17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  18 * Lesser General Public License for more details.
  19 * 
  20 * You should have received a copy of the GNU Lesser General Public
  21 * License along with this library; if not, write to the Free Software
  22 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
  23 * 
  24 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
  25 * $/LicenseInfo$
  26 */
  27
  28#include "linden_common.h"
  29
  30#include "llscrolllistctrl.h"
  31
  32#include <algorithm>
  33
  34#include "llstl.h"
  35#include "llboost.h"
  36//#include "indra_constants.h"
  37
  38#include "llcheckboxctrl.h"
  39#include "llclipboard.h"
  40#include "llfocusmgr.h"
  41#include "llgl.h"				// LLGLSUIDefault()
  42#include "lllocalcliprect.h"
  43//#include "llrender.h"
  44#include "llresmgr.h"
  45#include "llscrollbar.h"
  46#include "llscrolllistcell.h"
  47#include "llstring.h"
  48#include "llui.h"
  49#include "lluictrlfactory.h"
  50#include "llwindow.h"
  51#include "llcontrol.h"
  52#include "llkeyboard.h"
  53#include "llviewborder.h"
  54#include "lltextbox.h"
  55#include "llsdparam.h"
  56#include "llcachename.h"
  57#include "llmenugl.h"
  58#include "llurlaction.h"
  59#include "lltooltip.h"
  60
  61#include <boost/bind.hpp>
  62
  63static LLDefaultChildRegistry::Register<LLScrollListCtrl> r("scroll_list");
  64
  65// local structures & classes.
  66struct SortScrollListItem
  67{
  68	SortScrollListItem(const std::vector<std::pair<S32, BOOL> >& sort_orders,const LLScrollListCtrl::sort_signal_t*	sort_signal)
  69	:	mSortOrders(sort_orders)
  70	,   mSortSignal(sort_signal)
  71	{}
  72
  73	bool operator()(const LLScrollListItem* i1, const LLScrollListItem* i2)
  74	{
  75		// sort over all columns in order specified by mSortOrders
  76		S32 sort_result = 0;
  77		for (sort_order_t::const_reverse_iterator it = mSortOrders.rbegin();
  78			 it != mSortOrders.rend(); ++it)
  79		{
  80			S32 col_idx = it->first;
  81			BOOL sort_ascending = it->second;
  82
  83			S32 order = sort_ascending ? 1 : -1; // ascending or descending sort for this column?
  84
  85			const LLScrollListCell *cell1 = i1->getColumn(col_idx);
  86			const LLScrollListCell *cell2 = i2->getColumn(col_idx);
  87			if (cell1 && cell2)
  88			{
  89				if(mSortSignal)
  90				{
  91					sort_result = order * (*mSortSignal)(col_idx,i1, i2);
  92				}
  93				else
  94				{
  95					sort_result = order * LLStringUtil::compareDict(cell1->getValue().asString(), cell2->getValue().asString());
  96				}
  97				if (sort_result != 0)
  98				{
  99					break; // we have a sort order!
 100				}
 101			}
 102		}
 103
 104		return sort_result < 0;
 105	}
 106	
 107
 108	typedef std::vector<std::pair<S32, BOOL> > sort_order_t;
 109	const LLScrollListCtrl::sort_signal_t* mSortSignal;
 110	const sort_order_t& mSortOrders;
 111};
 112
 113//---------------------------------------------------------------------------
 114// LLScrollListCtrl
 115//---------------------------------------------------------------------------
 116
 117LLScrollListCtrl::Contents::Contents()
 118:	columns("column"),
 119	rows("row")
 120{
 121	addSynonym(columns, "columns");
 122	addSynonym(rows, "rows");
 123}
 124
 125LLScrollListCtrl::Params::Params()
 126:	multi_select("multi_select", false),
 127	has_border("draw_border"),
 128	draw_heading("draw_heading"),
 129	search_column("search_column", 0),
 130	sort_column("sort_column", -1),
 131	sort_ascending("sort_ascending", true),
 132	mouse_wheel_opaque("mouse_wheel_opaque", false),
 133	commit_on_keyboard_movement("commit_on_keyboard_movement", true),
 134	heading_height("heading_height"),
 135	page_lines("page_lines", 0),
 136	background_visible("background_visible"),
 137	draw_stripes("draw_stripes"),
 138	column_padding("column_padding"),
 139	fg_unselected_color("fg_unselected_color"),
 140	fg_selected_color("fg_selected_color"),
 141	bg_selected_color("bg_selected_color"),
 142	fg_disable_color("fg_disable_color"),
 143	bg_writeable_color("bg_writeable_color"),
 144	bg_readonly_color("bg_readonly_color"),
 145	bg_stripe_color("bg_stripe_color"),
 146	hovered_color("hovered_color"),
 147	highlighted_color("highlighted_color"),
 148	contents(""),
 149	scroll_bar_bg_visible("scroll_bar_bg_visible"),
 150	scroll_bar_bg_color("scroll_bar_bg_color"), 
 151	border("border")
 152{}
 153
 154LLScrollListCtrl::LLScrollListCtrl(const LLScrollListCtrl::Params& p)
 155:	LLUICtrl(p),
 156	mLineHeight(0),
 157	mScrollLines(0),
 158	mMouseWheelOpaque(p.mouse_wheel_opaque),
 159	mPageLines(p.page_lines),
 160	mMaxSelectable(0),
 161	mAllowKeyboardMovement(TRUE),
 162	mCommitOnKeyboardMovement(p.commit_on_keyboard_movement),
 163	mCommitOnSelectionChange(FALSE),
 164	mSelectionChanged(FALSE),
 165	mNeedsScroll(FALSE),
 166	mCanSelect(TRUE),
 167	mColumnsDirty(FALSE),
 168	mMaxItemCount(INT_MAX), 
 169	mMaxContentWidth(0),
 170	mBorderThickness( 2 ),
 171	mOnDoubleClickCallback( NULL ),
 172	mOnMaximumSelectCallback( NULL ),
 173	mOnSortChangedCallback( NULL ),
 174	mHighlightedItem(-1),
 175	mBorder(NULL),
 176	mSortCallback(NULL),
 177	mPopupMenu(NULL),
 178	mCommentTextView(NULL),
 179	mNumDynamicWidthColumns(0),
 180	mTotalStaticColumnWidth(0),
 181	mTotalColumnPadding(0),
 182	mSorted(false),
 183	mDirty(FALSE),
 184	mOriginalSelection(-1),
 185	mLastSelected(NULL),
 186	mHeadingHeight(p.heading_height),
 187	mAllowMultipleSelection(p.multi_select),
 188	mDisplayColumnHeaders(p.draw_heading),
 189	mBackgroundVisible(p.background_visible),
 190	mDrawStripes(p.draw_stripes),
 191	mBgWriteableColor(p.bg_writeable_color()),
 192	mBgReadOnlyColor(p.bg_readonly_color()),
 193	mBgSelectedColor(p.bg_selected_color()),
 194	mBgStripeColor(p.bg_stripe_color()),
 195	mFgSelectedColor(p.fg_selected_color()),
 196	mFgUnselectedColor(p.fg_unselected_color()),
 197	mFgDisabledColor(p.fg_disable_color()),
 198	mHighlightedColor(p.highlighted_color()),
 199	mHoveredColor(p.hovered_color()),
 200	mSearchColumn(p.search_column),
 201	mColumnPadding(p.column_padding),
 202	mContextMenuType(MENU_NONE)
 203{
 204	mItemListRect.setOriginAndSize(
 205		mBorderThickness,
 206		mBorderThickness,
 207		getRect().getWidth() - 2 * mBorderThickness,
 208		getRect().getHeight() - 2 * mBorderThickness );
 209
 210	updateLineHeight();
 211
 212	// Init the scrollbar
 213	static LLUICachedControl<S32> scrollbar_size ("UIScrollbarSize", 0);
 214
 215	LLRect scroll_rect;
 216	scroll_rect.setOriginAndSize( 
 217		getRect().getWidth() - mBorderThickness - scrollbar_size,
 218		mItemListRect.mBottom,
 219		scrollbar_size,
 220		mItemListRect.getHeight());
 221
 222	LLScrollbar::Params sbparams;
 223	sbparams.name("Scrollbar");
 224	sbparams.rect(scroll_rect);
 225	sbparams.orientation(LLScrollbar::VERTICAL);
 226	sbparams.doc_size(getItemCount());
 227	sbparams.doc_pos(mScrollLines);
 228	sbparams.page_size( getLinesPerPage() );
 229	sbparams.change_callback(boost::bind(&LLScrollListCtrl::onScrollChange, this, _1, _2));
 230	sbparams.follows.flags(FOLLOWS_RIGHT | FOLLOWS_TOP | FOLLOWS_BOTTOM);
 231	sbparams.visible(false);
 232	sbparams.bg_visible(p.scroll_bar_bg_visible);
 233	sbparams.bg_color(p.scroll_bar_bg_color);
 234	mScrollbar = LLUICtrlFactory::create<LLScrollbar> (sbparams);
 235	addChild(mScrollbar);
 236
 237	// Border
 238	if (p.has_border)
 239	{
 240		LLRect border_rect = getLocalRect();
 241		LLViewBorder::Params params = p.border;
 242		params.rect(border_rect);
 243		mBorder = LLUICtrlFactory::create<LLViewBorder> (params);
 244		addChild(mBorder);
 245	}
 246
 247	// set border *after* rect is fully initialized
 248	if (mBorder)
 249	{
 250		mBorder->setRect(getLocalRect());
 251		mBorder->reshape(getRect().getWidth(), getRect().getHeight());
 252	}
 253
 254	if (p.sort_column >= 0)
 255	{
 256		sortByColumnIndex(p.sort_column, p.sort_ascending);
 257	}
 258
 259	
 260	for (LLInitParam::ParamIterator<LLScrollListColumn::Params>::const_iterator row_it = p.contents.columns.begin();
 261		row_it != p.contents.columns.end();
 262		++row_it)
 263	{
 264		addColumn(*row_it);
 265	}
 266
 267	for (LLInitParam::ParamIterator<LLScrollListItem::Params>::const_iterator row_it = p.contents.rows.begin();
 268		row_it != p.contents.rows.end();
 269		++row_it)
 270	{
 271		addRow(*row_it);
 272	}
 273
 274	LLTextBox::Params text_p;
 275	text_p.name("comment_text");
 276	text_p.border_visible(false);
 277	text_p.rect(mItemListRect);
 278	text_p.follows.flags(FOLLOWS_ALL);
 279	// word wrap was added accroding to the EXT-6841
 280	text_p.wrap(true);
 281	addChild(LLUICtrlFactory::create<LLTextBox>(text_p));
 282}
 283
 284S32 LLScrollListCtrl::getSearchColumn()
 285{
 286	// search for proper search column
 287	if (mSearchColumn < 0)
 288	{
 289		LLScrollListItem* itemp = getFirstData();
 290		if (itemp)
 291		{
 292			for(S32 column = 0; column < getNumColumns(); column++)
 293			{
 294				LLScrollListCell* cell = itemp->getColumn(column);
 295				if (cell && cell->isText())
 296				{
 297					mSearchColumn = column;
 298					break;
 299				}
 300			}
 301		}
 302	}
 303	return llclamp(mSearchColumn, 0, getNumColumns());
 304}
 305/*virtual*/
 306bool LLScrollListCtrl::preProcessChildNode(LLXMLNodePtr child)
 307{
 308	if (child->hasName("column") || child->hasName("row"))
 309	{
 310		return true; // skip
 311	}
 312	else
 313	{
 314		return false;
 315	}
 316}
 317
 318LLScrollListCtrl::~LLScrollListCtrl()
 319{
 320	delete mSortCallback;
 321
 322	std::for_each(mItemList.begin(), mItemList.end(), DeletePointer());
 323	std::for_each(mColumns.begin(), mColumns.end(), DeletePairedPointer());
 324}
 325
 326
 327BOOL LLScrollListCtrl::setMaxItemCount(S32 max_count)
 328{
 329	if (max_count >= getItemCount())
 330	{
 331		mMaxItemCount = max_count;
 332	}
 333	return (max_count == mMaxItemCount);
 334}
 335
 336S32 LLScrollListCtrl::isEmpty() const
 337{
 338	return mItemList.empty();
 339}
 340
 341S32 LLScrollListCtrl::getItemCount() const
 342{
 343	return mItemList.size();
 344}
 345
 346// virtual LLScrolListInterface function (was deleteAllItems)
 347void LLScrollListCtrl::clearRows()
 348{
 349	std::for_each(mItemList.begin(), mItemList.end(), DeletePointer());
 350	mItemList.clear();
 351	//mItemCount = 0;
 352
 353	// Scroll the bar back up to the top.
 354	mScrollbar->setDocParams(0, 0);
 355
 356	mScrollLines = 0;
 357	mLastSelected = NULL;
 358	updateLayout();
 359	mDirty = FALSE; 
 360}
 361
 362
 363LLScrollListItem* LLScrollListCtrl::getFirstSelected() const
 364{
 365	item_list::const_iterator iter;
 366	for(iter = mItemList.begin(); iter != mItemList.end(); iter++)
 367	{
 368		LLScrollListItem* item  = *iter;
 369		if (item->getSelected())
 370		{
 371			return item;
 372		}
 373	}
 374	return NULL;
 375}
 376
 377std::vector<LLScrollListItem*> LLScrollListCtrl::getAllSelected() const
 378{
 379	std::vector<LLScrollListItem*> ret;
 380	item_list::const_iterator iter;
 381	for(iter = mItemList.begin(); iter != mItemList.end(); iter++)
 382	{
 383		LLScrollListItem* item  = *iter;
 384		if (item->getSelected())
 385		{
 386			ret.push_back(item);
 387		}
 388	}
 389	return ret;
 390}
 391
 392S32 LLScrollListCtrl::getFirstSelectedIndex() const
 393{
 394	S32 CurSelectedIndex = 0;
 395
 396	// make sure sort is up to date before returning an index
 397	updateSort();
 398
 399	item_list::const_iterator iter;
 400	for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
 401	{
 402		LLScrollListItem* item  = *iter;
 403		if (item->getSelected())
 404		{
 405			return CurSelectedIndex;
 406		}
 407		CurSelectedIndex++;
 408	}
 409
 410	return -1;
 411}
 412
 413LLScrollListItem* LLScrollListCtrl::getFirstData() const
 414{
 415	if (mItemList.size() == 0)
 416	{
 417		return NULL;
 418	}
 419	return mItemList[0];
 420}
 421
 422LLScrollListItem* LLScrollListCtrl::getLastData() const
 423{
 424	if (mItemList.size() == 0)
 425	{
 426		return NULL;
 427	}
 428	return mItemList[mItemList.size() - 1];
 429}
 430
 431std::vector<LLScrollListItem*> LLScrollListCtrl::getAllData() const
 432{
 433	std::vector<LLScrollListItem*> ret;
 434	item_list::const_iterator iter;
 435	for(iter = mItemList.begin(); iter != mItemList.end(); iter++)
 436	{
 437		LLScrollListItem* item  = *iter;
 438		ret.push_back(item);
 439	}
 440	return ret;
 441}
 442
 443// returns first matching item
 444LLScrollListItem* LLScrollListCtrl::getItem(const LLSD& sd) const
 445{
 446	std::string string_val = sd.asString();
 447
 448	item_list::const_iterator iter;
 449	for(iter = mItemList.begin(); iter != mItemList.end(); iter++)
 450	{
 451		LLScrollListItem* item  = *iter;
 452		// assumes string representation is good enough for comparison
 453		if (item->getValue().asString() == string_val)
 454		{
 455			return item;
 456		}
 457	}
 458	return NULL;
 459}
 460
 461
 462void LLScrollListCtrl::reshape( S32 width, S32 height, BOOL called_from_parent )
 463{
 464	LLUICtrl::reshape( width, height, called_from_parent );
 465
 466	updateLayout();
 467}
 468
 469void LLScrollListCtrl::updateLayout()
 470{
 471	static LLUICachedControl<S32> scrollbar_size ("UIScrollbarSize", 0);
 472	// reserve room for column headers, if needed
 473	S32 heading_size = (mDisplayColumnHeaders ? mHeadingHeight : 0);
 474	mItemListRect.setOriginAndSize(
 475		mBorderThickness,
 476		mBorderThickness,
 477		getRect().getWidth() - 2 * mBorderThickness,
 478		getRect().getHeight() - (2 * mBorderThickness ) - heading_size );
 479
 480	if (mCommentTextView == NULL)
 481	{
 482		mCommentTextView = getChildView("comment_text");
 483	}
 484
 485	mCommentTextView->setShape(mItemListRect);
 486
 487	// how many lines of content in a single "page"
 488	S32 page_lines =  getLinesPerPage();
 489
 490	BOOL scrollbar_visible = mLineHeight * getItemCount() > mItemListRect.getHeight();
 491	if (scrollbar_visible)
 492	{
 493		// provide space on the right for scrollbar
 494		mItemListRect.mRight = getRect().getWidth() - mBorderThickness - scrollbar_size;
 495	}
 496
 497	mScrollbar->setOrigin(getRect().getWidth() - mBorderThickness - scrollbar_size, mItemListRect.mBottom);
 498	mScrollbar->reshape(scrollbar_size, mItemListRect.getHeight() + (mDisplayColumnHeaders ? mHeadingHeight : 0));
 499	mScrollbar->setPageSize(page_lines);
 500	mScrollbar->setDocSize( getItemCount() );
 501	mScrollbar->setVisible(scrollbar_visible);
 502
 503	dirtyColumns();
 504}
 505
 506// Attempt to size the control to show all items.
 507// Do not make larger than width or height.
 508void LLScrollListCtrl::fitContents(S32 max_width, S32 max_height)
 509{
 510	S32 height = llmin( getRequiredRect().getHeight(), max_height );
 511	if(mPageLines)
 512		height = llmin( mPageLines * mLineHeight + 2*mBorderThickness + (mDisplayColumnHeaders ? mHeadingHeight : 0), height );
 513
 514	S32 width = getRect().getWidth();
 515
 516	reshape( width, height );
 517}
 518
 519
 520LLRect LLScrollListCtrl::getRequiredRect()
 521{
 522	S32 heading_size = (mDisplayColumnHeaders ? mHeadingHeight : 0);
 523	S32 height = (mLineHeight * getItemCount()) 
 524				+ (2 * mBorderThickness ) 
 525				+ heading_size;
 526	S32 width = getRect().getWidth();
 527
 528	return LLRect(0, height, width, 0);
 529}
 530
 531
 532BOOL LLScrollListCtrl::addItem( LLScrollListItem* item, EAddPosition pos, BOOL requires_column )
 533{
 534	BOOL not_too_big = getItemCount() < mMaxItemCount;
 535	if (not_too_big)
 536	{
 537		switch( pos )
 538		{
 539		case ADD_TOP:
 540			mItemList.push_front(item);
 541			setNeedsSort();
 542			break;
 543	
 544		case ADD_DEFAULT:
 545		case ADD_BOTTOM:
 546			mItemList.push_back(item);
 547			setNeedsSort();
 548			break;
 549	
 550		default:
 551			llassert(0);
 552			mItemList.push_back(item);
 553			setNeedsSort();
 554			break;
 555		}
 556	
 557		// create new column on demand
 558		if (mColumns.empty() && requires_column)
 559		{
 560			LLScrollListColumn::Params col_params;
 561			col_params.name =  "default_column";
 562			col_params.header.label = "";
 563			col_params.width.dynamic_width = true;
 564			addColumn(col_params);
 565		}
 566
 567		updateLineHeightInsert(item);
 568
 569		updateLayout();
 570	}
 571
 572	return not_too_big;
 573}
 574
 575// NOTE: This is *very* expensive for large lists, especially when we are dirtying the list every frame
 576//  while receiving a long list of names.
 577// *TODO: Use bookkeeping to make this an incramental cost with item additions
 578void LLScrollListCtrl::calcColumnWidths()
 579{
 580	const S32 HEADING_TEXT_PADDING = 25;
 581	const S32 COLUMN_TEXT_PADDING = 10;
 582
 583	mMaxContentWidth = 0;
 584
 585	S32 max_item_width = 0;
 586
 587	ordered_columns_t::iterator column_itor;
 588	for (column_itor = mColumnsIndexed.begin(); column_itor != mColumnsIndexed.end(); ++column_itor)
 589	{
 590		LLScrollListColumn* column = *column_itor;
 591		if (!column) continue;
 592
 593		// update column width
 594		S32 new_width = column->getWidth();
 595		if (column->mRelWidth >= 0)
 596		{
 597			new_width = (S32)llround(column->mRelWidth*mItemListRect.getWidth());
 598		}
 599		else if (column->mDynamicWidth)
 600		{
 601			new_width = (mItemListRect.getWidth() - mTotalStaticColumnWidth - mTotalColumnPadding) / mNumDynamicWidthColumns;
 602		}
 603
 604		column->setWidth(new_width);
 605
 606		// update max content width for this column, by looking at all items
 607		column->mMaxContentWidth = column->mHeader ? LLFontGL::getFontSansSerifSmall()->getWidth(column->mLabel) + mColumnPadding + HEADING_TEXT_PADDING : 0;
 608		item_list::iterator iter;
 609		for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
 610		{
 611			LLScrollListCell* cellp = (*iter)->getColumn(column->mIndex);
 612			if (!cellp) continue;
 613
 614			column->mMaxContentWidth = llmax(LLFontGL::getFontSansSerifSmall()->getWidth(cellp->getValue().asString()) + mColumnPadding + COLUMN_TEXT_PADDING, column->mMaxContentWidth);
 615		}
 616
 617		max_item_width += column->mMaxContentWidth;
 618	}
 619
 620	mMaxContentWidth = max_item_width;
 621}
 622
 623const S32 SCROLL_LIST_ROW_PAD = 2;
 624
 625// Line height is the max height of all the cells in all the items.
 626void LLScrollListCtrl::updateLineHeight()
 627{
 628	mLineHeight = 0;
 629	item_list::iterator iter;
 630	for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
 631	{
 632		LLScrollListItem *itemp = *iter;
 633		S32 num_cols = itemp->getNumColumns();
 634		S32 i = 0;
 635		for (const LLScrollListCell* cell = itemp->getColumn(i); i < num_cols; cell = itemp->getColumn(++i))
 636		{
 637			mLineHeight = llmax( mLineHeight, cell->getHeight() + SCROLL_LIST_ROW_PAD );
 638		}
 639	}
 640}
 641
 642// when the only change to line height is from an insert, we needn't scan the entire list
 643void LLScrollListCtrl::updateLineHeightInsert(LLScrollListItem* itemp)
 644{
 645	S32 num_cols = itemp->getNumColumns();
 646	S32 i = 0;
 647	for (const LLScrollListCell* cell = itemp->getColumn(i); i < num_cols; cell = itemp->getColumn(++i))
 648	{
 649		mLineHeight = llmax( mLineHeight, cell->getHeight() + SCROLL_LIST_ROW_PAD );
 650	}
 651}
 652
 653
 654void LLScrollListCtrl::updateColumns()
 655{
 656	calcColumnWidths();
 657
 658	// update column headers
 659	std::vector<LLScrollListColumn*>::iterator column_ordered_it;
 660	S32 left = mItemListRect.mLeft;
 661	LLScrollColumnHeader* last_header = NULL;
 662	for (column_ordered_it = mColumnsIndexed.begin(); column_ordered_it != mColumnsIndexed.end(); ++column_ordered_it)
 663	{
 664		if ((*column_ordered_it)->getWidth() < 0)
 665		{
 666			// skip hidden columns
 667			continue;
 668		}
 669		LLScrollListColumn* column = *column_ordered_it;
 670		
 671		if (column->mHeader)
 672		{
 673			column->mHeader->updateResizeBars();
 674
 675			last_header = column->mHeader;
 676			S32 top = mItemListRect.mTop;
 677			S32 right = left + column->getWidth();
 678
 679			if (column->mIndex != (S32)mColumnsIndexed.size()-1)
 680			{
 681				right += mColumnPadding;
 682			}
 683			right = llmax(left, llmin(mItemListRect.getWidth(), right));
 684			S32 header_width = right - left;
 685
 686			last_header->reshape(header_width, mHeadingHeight);
 687			last_header->translate(
 688				left - last_header->getRect().mLeft,
 689				top - last_header->getRect().mBottom);
 690			last_header->setVisible(mDisplayColumnHeaders && header_width > 0);
 691			left = right;
 692		}
 693	}
 694
 695	// expand last column header we encountered to full list width
 696	if (last_header)
 697	{
 698		S32 new_width = llmax(0, mItemListRect.mRight - last_header->getRect().mLeft);
 699		last_header->reshape(new_width, last_header->getRect().getHeight());
 700		last_header->setVisible(mDisplayColumnHeaders && new_width > 0);
 701		last_header->getColumn()->setWidth(new_width);
 702	}
 703
 704	// propagate column widths to individual cells
 705	item_list::iterator iter;
 706	for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
 707	{
 708		LLScrollListItem *itemp = *iter;
 709		S32 num_cols = itemp->getNumColumns();
 710		S32 i = 0;
 711		for (LLScrollListCell* cell = itemp->getColumn(i); i < num_cols; cell = itemp->getColumn(++i))
 712		{
 713			if (i >= (S32)mColumnsIndexed.size()) break;
 714
 715			cell->setWidth(mColumnsIndexed[i]->getWidth());
 716		}
 717	}
 718
 719}
 720
 721void LLScrollListCtrl::setHeadingHeight(S32 heading_height)
 722{
 723	mHeadingHeight = heading_height;
 724
 725	updateLayout();
 726
 727}
 728void LLScrollListCtrl::setPageLines(S32 new_page_lines)
 729{
 730	mPageLines  = new_page_lines;
 731	
 732	updateLayout();
 733}
 734
 735BOOL LLScrollListCtrl::selectFirstItem()
 736{
 737	BOOL success = FALSE;
 738
 739	// our $%&@#$()^%#$()*^ iterators don't let us check against the first item inside out iteration
 740	BOOL first_item = TRUE;
 741
 742	item_list::iterator iter;
 743	for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
 744	{
 745		LLScrollListItem *itemp = *iter;
 746		if( first_item && itemp->getEnabled() )
 747		{
 748			if (!itemp->getSelected())
 749			{
 750				selectItem(itemp);
 751			}
 752			success = TRUE;
 753			mOriginalSelection = 0;
 754		}
 755		else
 756		{
 757			deselectItem(itemp);
 758		}
 759		first_item = FALSE;
 760	}
 761	if (mCommitOnSelectionChange)
 762	{
 763		commitIfChanged();
 764	}
 765	return success;
 766}
 767
 768// Deselects all other items
 769// virtual
 770BOOL LLScrollListCtrl::selectNthItem( S32 target_index )
 771{
 772	return selectItemRange(target_index, target_index);
 773}
 774
 775// virtual
 776BOOL LLScrollListCtrl::selectItemRange( S32 first_index, S32 last_index )
 777{
 778	if (mItemList.empty())
 779	{
 780		return FALSE;
 781	}
 782
 783	// make sure sort is up to date
 784	updateSort();
 785
 786	S32 listlen = (S32)mItemList.size();
 787	first_index = llclamp(first_index, 0, listlen-1);
 788	
 789	if (last_index < 0)
 790		last_index = listlen-1;
 791	else
 792		last_index = llclamp(last_index, first_index, listlen-1);
 793
 794	BOOL success = FALSE;
 795	S32 index = 0;
 796	for (item_list::iterator iter = mItemList.begin(); iter != mItemList.end(); )
 797	{
 798		LLScrollListItem *itemp = *iter;
 799		if(!itemp)
 800		{
 801			iter = mItemList.erase(iter);
 802			continue ;
 803		}
 804		
 805		if( index >= first_index && index <= last_index )
 806		{
 807			if( itemp->getEnabled() )
 808			{
 809				selectItem(itemp, FALSE);
 810				success = TRUE;				
 811			}
 812		}
 813		else
 814		{
 815			deselectItem(itemp);
 816		}
 817		index++;
 818		iter++ ;
 819	}
 820
 821	if (mCommitOnSelectionChange)
 822	{
 823		commitIfChanged();
 824	}
 825
 826	mSearchString.clear();
 827
 828	return success;
 829}
 830
 831
 832void LLScrollListCtrl::swapWithNext(S32 index)
 833{
 834	if (index >= ((S32)mItemList.size() - 1))
 835	{
 836		// At end of list, doesn't do anything
 837		return;
 838	}
 839	updateSort();
 840	LLScrollListItem *cur_itemp = mItemList[index];
 841	mItemList[index] = mItemList[index + 1];
 842	mItemList[index + 1] = cur_itemp;
 843}
 844
 845
 846void LLScrollListCtrl::swapWithPrevious(S32 index)
 847{
 848	if (index <= 0)
 849	{
 850		// At beginning of list, don't do anything
 851	}
 852
 853	updateSort();
 854	LLScrollListItem *cur_itemp = mItemList[index];
 855	mItemList[index] = mItemList[index - 1];
 856	mItemList[index - 1] = cur_itemp;
 857}
 858
 859
 860void LLScrollListCtrl::deleteSingleItem(S32 target_index)
 861{
 862	if (target_index < 0 || target_index >= (S32)mItemList.size())
 863	{
 864		return;
 865	}
 866
 867	updateSort();
 868
 869	LLScrollListItem *itemp;
 870	itemp = mItemList[target_index];
 871	if (itemp == mLastSelected)
 872	{
 873		mLastSelected = NULL;
 874	}
 875	delete itemp;
 876	mItemList.erase(mItemList.begin() + target_index);
 877	dirtyColumns();
 878}
 879
 880//FIXME: refactor item deletion
 881void LLScrollListCtrl::deleteItems(const LLSD& sd)
 882{
 883	item_list::iterator iter;
 884	for (iter = mItemList.begin(); iter < mItemList.end(); )
 885	{
 886		LLScrollListItem* itemp = *iter;
 887		if (itemp->getValue().asString() == sd.asString())
 888		{
 889			if (itemp == mLastSelected)
 890			{
 891				mLastSelected = NULL;
 892			}
 893			delete itemp;
 894			iter = mItemList.erase(iter);
 895		}
 896		else
 897		{
 898			iter++;
 899		}
 900	}
 901
 902	dirtyColumns();
 903}
 904
 905void LLScrollListCtrl::deleteSelectedItems()
 906{
 907	item_list::iterator iter;
 908	for (iter = mItemList.begin(); iter < mItemList.end(); )
 909	{
 910		LLScrollListItem* itemp = *iter;
 911		if (itemp->getSelected())
 912		{
 913			delete itemp;
 914			iter = mItemList.erase(iter);
 915		}
 916		else
 917		{
 918			iter++;
 919		}
 920	}
 921	mLastSelected = NULL;
 922	dirtyColumns();
 923}
 924
 925void LLScrollListCtrl::clearHighlightedItems()
 926{	
 927	for (item_list::iterator iter = mItemList.begin(); iter != mItemList.end(); ++iter)
 928	{
 929		(*iter)->setHighlighted(false);
 930	}
 931}
 932
 933void LLScrollListCtrl::mouseOverHighlightNthItem(S32 target_index)
 934{
 935	if (mHighlightedItem != target_index)
 936	{
 937		mHighlightedItem = target_index;
 938	}
 939}
 940
 941S32	LLScrollListCtrl::selectMultiple( uuid_vec_t ids )
 942{
 943	item_list::iterator iter;
 944	S32 count = 0;
 945	for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
 946	{
 947		LLScrollListItem* item = *iter;
 948		uuid_vec_t::iterator iditr;
 949		for(iditr = ids.begin(); iditr != ids.end(); ++iditr)
 950		{
 951			if (item->getEnabled() && (item->getUUID() == (*iditr)))
 952			{
 953				selectItem(item,FALSE);
 954				++count;
 955				break;
 956			}
 957		}
 958		if(ids.end() != iditr) ids.erase(iditr);
 959	}
 960
 961	if (mCommitOnSelectionChange)
 962	{
 963		commitIfChanged();
 964	}
 965	return count;
 966}
 967
 968S32 LLScrollListCtrl::getItemIndex( LLScrollListItem* target_item ) const
 969{
 970	updateSort();
 971
 972	S32 index = 0;
 973	item_list::const_iterator iter;
 974	for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
 975	{
 976		LLScrollListItem *itemp = *iter;
 977		if (target_item == itemp)
 978		{
 979			return index;
 980		}
 981		index++;
 982	}
 983	return -1;
 984}
 985
 986S32 LLScrollListCtrl::getItemIndex( const LLUUID& target_id ) const
 987{
 988	updateSort();
 989
 990	S32 index = 0;
 991	item_list::const_iterator iter;
 992	for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
 993	{
 994		LLScrollListItem *itemp = *iter;
 995		if (target_id == itemp->getUUID())
 996		{
 997			return index;
 998		}
 999		index++;
1000	}
1001	return -1;
1002}
1003
1004void LLScrollListCtrl::selectPrevItem( BOOL extend_selection)
1005{
1006	LLScrollListItem* prev_item = NULL;
1007
1008	if (!getFirstSelected())
1009	{
1010		// select last item
1011		selectNthItem(getItemCount() - 1);
1012	}
1013	else
1014	{
1015		updateSort();
1016
1017		item_list::iterator iter;
1018		for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
1019		{
1020			LLScrollListItem* cur_item = *iter;
1021
1022			if (cur_item->getSelected())
1023			{
1024				if (prev_item)
1025				{
1026					selectItem(prev_item, !extend_selection);
1027				}
1028				else
1029				{
1030					reportInvalidInput();
1031				}
1032				break;
1033			}
1034
1035			// don't allow navigation to disabled elements
1036			prev_item = cur_item->getEnabled() ? cur_item : prev_item;
1037		}
1038	}
1039
1040	if ((mCommitOnSelectionChange || mCommitOnKeyboardMovement))
1041	{
1042		commitIfChanged();
1043	}
1044
1045	mSearchString.clear();
1046}
1047
1048
1049void LLScrollListCtrl::selectNextItem( BOOL extend_selection)
1050{
1051	LLScrollListItem* next_item = NULL;
1052
1053	if (!getFirstSelected())
1054	{
1055		selectFirstItem();
1056	}
1057	else
1058	{
1059		updateSort();
1060
1061		item_list::reverse_iterator iter;
1062		for (iter = mItemList.rbegin(); iter != mItemList.rend(); iter++)
1063		{
1064			LLScrollListItem* cur_item = *iter;
1065
1066			if (cur_item->getSelected())
1067			{
1068				if (next_item)
1069				{
1070					selectItem(next_item, !extend_selection);
1071				}
1072				else
1073				{
1074					reportInvalidInput();
1075				}
1076				break;
1077			}
1078
1079			// don't allow navigation to disabled items
1080			next_item = cur_item->getEnabled() ? cur_item : next_item;
1081		}
1082	}
1083
1084	if (mCommitOnKeyboardMovement)
1085	{
1086		onCommit();
1087	}
1088
1089	mSearchString.clear();
1090}
1091
1092
1093
1094void LLScrollListCtrl::deselectAllItems(BOOL no_commit_on_change)
1095{
1096	item_list::iterator iter;
1097	for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
1098	{
1099		LLScrollListItem* item = *iter;
1100		deselectItem(item);
1101	}
1102
1103	if (mCommitOnSelectionChange && !no_commit_on_change)
1104	{
1105		commitIfChanged();
1106	}
1107}
1108
1109///////////////////////////////////////////////////////////////////////////////////////////////////
1110// Use this to add comment text such as "Searching", which ignores column settings of list
1111
1112void LLScrollListCtrl::setCommentText(const std::string& comment_text)
1113{
1114	getChild<LLTextBox>("comment_text")->setValue(comment_text);
1115}
1116
1117LLScrollListItem* LLScrollListCtrl::addSeparator(EAddPosition pos)
1118{
1119	LLScrollListItem::Params separator_params;
1120	separator_params.enabled(false);
1121	LLScrollListCell::Params column_params;
1122	column_params.type = "icon";
1123	column_params.value = "menu_separator";
1124	column_params.color = LLColor4(0.f, 0.f, 0.f, 0.7f);
1125	column_params.font_halign = LLFontGL::HCENTER;
1126	separator_params.columns.add(column_params);
1127	return addRow( separator_params, pos );
1128}
1129
1130// Selects first enabled item of the given name.
1131// Returns false if item not found.
1132// Calls getItemByLabel in order to combine functionality
1133BOOL LLScrollListCtrl::selectItemByLabel(const std::string& label, BOOL case_sensitive)
1134{
1135	deselectAllItems(TRUE); 	// ensure that no stale items are selected, even if we don't find a match
1136	LLScrollListItem* item = getItemByLabel(label, case_sensitive);
1137
1138	bool found = NULL != item;
1139	if(found)
1140	{
1141		selectItem(item);
1142	}
1143
1144	if (mCommitOnSelectionChange)
1145	{
1146		commitIfChanged();
1147	}
1148
1149	return found;
1150}
1151
1152LLScrollListItem* LLScrollListCtrl::getItemByLabel(const std::string& label, BOOL case_sensitive, S32 column)
1153{
1154	if (label.empty()) 	//RN: assume no empty items
1155	{
1156		return NULL;
1157	}
1158
1159	std::string target_text = label;
1160	if (!case_sensitive)
1161	{
1162		LLStringUtil::toLower(target_text);
1163	}
1164
1165	item_list::iterator iter;
1166	for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
1167	{
1168		LLScrollListItem* item = *iter;
1169		std::string item_text = item->getColumn(column)->getValue().asString();	// Only select enabled items with matching names
1170		if (!case_sensitive)
1171		{
1172			LLStringUtil::toLower(item_text);
1173		}
1174		if(item_text == target_text)
1175		{
1176			return item;
1177		}
1178	}
1179	return NULL;
1180}
1181
1182
1183BOOL LLScrollListCtrl::selectItemByPrefix(const std::string& target, BOOL case_sensitive)
1184{
1185	return selectItemByPrefix(utf8str_to_wstring(target), case_sensitive);
1186}
1187
1188// Selects first enabled item that has a name where the name's first part matched the target string.
1189// Returns false if item not found.
1190BOOL LLScrollListCtrl::selectItemByPrefix(const LLWString& target, BOOL case_sensitive)
1191{
1192	BOOL found = FALSE;
1193
1194	LLWString target_trimmed( target );
1195	S32 target_len = target_trimmed.size();
1196	
1197	if( 0 == target_len )
1198	{
1199		// Is "" a valid choice?
1200		item_list::iterator iter;
1201		for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
1202		{
1203			LLScrollListItem* item = *iter;
1204			// Only select enabled items with matching names
1205			LLScrollListCell* cellp = item->getColumn(getSearchColumn());
1206			BOOL select = cellp ? item->getEnabled() && ('\0' == cellp->getValue().asString()[0]) : FALSE;
1207			if (select)
1208			{
1209				selectItem(item);
1210				found = TRUE;
1211				break;
1212			}
1213		}
1214	}
1215	else
1216	{
1217		if (!case_sensitive)
1218		{
1219			// do comparisons in lower case
1220			LLWStringUtil::toLower(target_trimmed);
1221		}
1222
1223		for (item_list::iterator iter = mItemList.begin(); iter != mItemList.end(); iter++)
1224		{
1225			LLScrollListItem* item = *iter;
1226
1227			// Only select enabled items with matching names
1228			LLScrollListCell* cellp = item->getColumn(getSearchColumn());
1229			if (!cellp)
1230			{
1231				continue;
1232			}
1233			LLWString item_label = utf8str_to_wstring(cellp->getValue().asString());
1234			if (!case_sensitive)
1235			{
1236				LLWStringUtil::toLower(item_label);
1237			}
1238			// remove extraneous whitespace from searchable label
1239			LLWString trimmed_label = item_label;
1240			LLWStringUtil::trim(trimmed_label);
1241			
1242			BOOL select = item->getEnabled() && trimmed_label.compare(0, target_trimmed.size(), target_trimmed) == 0;
1243
1244			if (select)
1245			{
1246				// find offset of matching text (might have leading whitespace)
1247				S32 offset = item_label.find(target_trimmed);
1248				cellp->highlightText(offset, target_trimmed.size());
1249				selectItem(item);
1250				found = TRUE;
1251				break;
1252			}
1253		}
1254	}
1255
1256	if (mCommitOnSelectionChange)
1257	{
1258		commitIfChanged();
1259	}
1260
1261	return found;
1262}
1263
1264const std::string LLScrollListCtrl::getSelectedItemLabel(S32 column) const
1265{
1266	LLScrollListItem* item;
1267
1268	item = getFirstSelected();
1269	if (item)
1270	{
1271		return item->getColumn(column)->getValue().asString();
1272	}
1273
1274	return LLStringUtil::null;
1275}
1276
1277///////////////////////////////////////////////////////////////////////////////////////////////////
1278// "StringUUID" interface: use this when you're creating a list that contains non-unique strings each of which
1279// has an associated, unique UUID, and only one of which can be selected at a time.
1280
1281LLScrollListItem* LLScrollListCtrl::addStringUUIDItem(const std::string& item_text, const LLUUID& id, EAddPosition pos, BOOL enabled)
1282{
1283	if (getItemCount() < mMaxItemCount)
1284	{
1285		LLScrollListItem::Params item_p;
1286		item_p.enabled(enabled);
1287		item_p.value(id);
1288		item_p.columns.add().value(item_text).type("text");
1289
1290		return addRow( item_p, pos );
1291	}
1292	return NULL;
1293}
1294
1295// Select the line or lines that match this UUID
1296BOOL LLScrollListCtrl::selectByID( const LLUUID& id )
1297{
1298	return selectByValue( LLSD(id) );
1299}
1300
1301BOOL LLScrollListCtrl::setSelectedByValue(const LLSD& value, BOOL selected)
1302{
1303	BOOL found = FALSE;
1304
1305	if (selected && !mAllowMultipleSelection) deselectAllItems(TRUE);
1306
1307	item_list::iterator iter;
1308	for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
1309	{
1310		LLScrollListItem* item = *iter;
1311		if (item->getEnabled() && (item->getValue().asString() == value.asString()))
1312		{
1313			if (selected)
1314			{
1315				selectItem(item);
1316			}
1317			else
1318			{
1319				deselectItem(item);
1320			}
1321			found = TRUE;
1322			break;
1323		}
1324	}
1325
1326	if (mCommitOnSelectionChange)
1327	{
1328		commitIfChanged();
1329	}
1330
1331	return found;
1332}
1333
1334BOOL LLScrollListCtrl::isSelected(const LLSD& value) const 
1335{
1336	item_list::const_iterator iter;
1337	for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
1338	{
1339		LLScrollListItem* item = *iter;
1340		if (item->getValue().asString() == value.asString())
1341		{
1342			return item->getSelected();
1343		}
1344	}
1345	return FALSE;
1346}
1347
1348LLUUID LLScrollListCtrl::getStringUUIDSelectedItem() const
1349{
1350	LLScrollListItem* item = getFirstSelected();
1351
1352	if (item)
1353	{
1354		return item->getUUID();
1355	}
1356
1357	return LLUUID::null;
1358}
1359
1360LLSD LLScrollListCtrl::getSelectedValue()
1361{
1362	LLScrollListItem* item = getFirstSelected();
1363
1364	if (item)
1365	{
1366		return item->getValue();
1367	}
1368	else
1369	{
1370		return LLSD();
1371	}
1372}
1373
1374void LLScrollListCtrl::drawItems()
1375{
1376	S32 x = mItemListRect.mLeft;
1377	S32 y = mItemListRect.mTop - mLineHeight;
1378
1379	// allow for partial line at bottom
1380	S32 num_page_lines = getLinesPerPage();
1381
1382	LLRect item_rect;
1383
1384	LLGLSUIDefault gls_ui;
1385	
1386	F32 alpha = getDrawContext().mAlpha;
1387
1388	{
1389		LLLocalClipRect clip(mItemListRect);
1390
1391		S32 cur_y = y;
1392		
1393		S32 line = 0;
1394		S32 max_columns = 0;
1395
1396		LLColor4 highlight_color = LLColor4::white;
1397		static LLUICachedControl<F32> type_ahead_timeout ("TypeAheadTimeout", 0);
1398		highlight_color.mV[VALPHA] = clamp_rescale(mSearchTimer.getElapsedTimeF32(), type_ahead_timeout * 0.7f, type_ahead_timeout, 0.4f, 0.f);
1399
1400		item_list::iterator iter;
1401		for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
1402		{
1403			LLScrollListItem* item = *iter;
1404			
1405			item_rect.setOriginAndSize( 
1406				x, 
1407				cur_y, 
1408				mItemListRect.getWidth(),
1409				mLineHeight );
1410			item->setRect(item_rect);
1411
1412			//llinfos << item_rect.getWidth() << llendl;
1413
1414			max_columns = llmax(max_columns, item->getNumColumns());
1415
1416			LLColor4 fg_color;
1417			LLColor4 bg_color(LLColor4::transparent);
1418
1419			if( mScrollLines <= line && line < mScrollLines + num_page_lines )
1420			{
1421				fg_color = (item->getEnabled() ? mFgUnselectedColor.get() : mFgDisabledColor.get());
1422				if( item->getSelected() && mCanSelect)
1423				{
1424					if(item->getHighlighted())	// if it's highlighted, average the colors
1425					{
1426						bg_color = lerp(mBgSelectedColor.get(), mHighlightedColor.get(), 0.5f);
1427					}
1428					else						// otherwise just select-highlight it
1429					{
1430						bg_color = mBgSelectedColor.get();
1431					}
1432
1433					fg_color = (item->getEnabled() ? mFgSelectedColor.get() : mFgDisabledColor.get());
1434				}
1435				else if (mHighlightedItem == line && mCanSelect)
1436				{
1437					if(item->getHighlighted())	// if it's highlighted, average the colors
1438					{
1439						bg_color = lerp(mHoveredColor.get(), mHighlightedColor.get(), 0.5f);
1440					}
1441					else						// otherwise just hover-highlight it
1442					{
1443						bg_color = mHoveredColor.get();
1444					}
1445				}
1446				else if (item->getHighlighted())
1447				{
1448					bg_color = mHighlightedColor.get();
1449				}
1450				else 
1451				{
1452					if (mDrawStripes && (line % 2 == 0) && (max_columns > 1))
1453					{
1454						bg_color = mBgStripeColor.get();
1455					}
1456				}
1457
1458				if (!item->getEnabled())
1459				{
1460					bg_color = mBgReadOnlyColor.get();
1461				}
1462
1463				item->draw(item_rect, fg_color % alpha, bg_color% alpha, highlight_color % alpha, mColumnPadding);
1464
1465				cur_y -= mLineHeight;
1466			}
1467			line++;
1468		}
1469	}
1470}
1471
1472
1473void LLScrollListCtrl::draw()
1474{
1475	LLLocalClipRect clip(getLocalRect());
1476
1477	// if user specifies sort, make sure it is maintained
1478	updateSort();
1479
1480	if (mNeedsScroll)
1481	{
1482		scrollToShowSelected();
1483		mNeedsScroll = FALSE;
1484	}
1485	LLRect background(0, getRect().getHeight(), getRect().getWidth(), 0);
1486	// Draw background
1487	if (mBackgroundVisible)
1488	{
1489		F32 alpha = getCurrentTransparency();
1490		gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
1491		gl_rect_2d(background, getEnabled() ? mBgWriteableColor.get() % alpha : mBgReadOnlyColor.get() % alpha );
1492	}
1493
1494	if (mColumnsDirty)
1495	{
1496		updateColumns();
1497		mColumnsDirty = FALSE;
1498	}
1499
1500	getChildView("comment_text")->setVisible(mItemList.empty());
1501
1502	drawItems();
1503
1504	if (mBorder)
1505	{
1506		mBorder->setKeyboardFocusHighlight(hasFocus());
1507	}
1508
1509	LLUICtrl::draw();
1510}
1511
1512void LLScrollListCtrl::setEnabled(BOOL enabled)
1513{
1514	mCanSelect = enabled;
1515	setTabStop(enabled);
1516	mScrollbar->setTabStop(!enabled && mScrollbar->getPageSize() < mScrollbar->getDocSize());
1517}
1518
1519BOOL LLScrollListCtrl::handleScrollWheel(S32 x, S32 y, S32 clicks)
1520{
1521	BOOL handled = FALSE;
1522	// Pretend the mouse is over the scrollbar
1523	handled = mScrollbar->handleScrollWheel( 0, 0, clicks );
1524
1525	if (mMouseWheelOpaque)
1526	{
1527		return TRUE;
1528	}
1529
1530	return handled;
1531}
1532
1533// *NOTE: Requires a valid row_index and column_index
1534LLRect LLScrollListCtrl::getCellRect(S32 row_index, S32 column_index)
1535{
1536	LLRect cell_rect;
1537	S32 rect_left = getColumnOffsetFromIndex(column_index) + mItemListRect.mLeft;
1538	S32 rect_bottom = getRowOffsetFromIndex(row_index);
1539	LLScrollListColumn* columnp = getColumn(column_index);
1540	cell_rect.setOriginAndSize(rect_left, rect_bottom,
1541		/*rect_left + */columnp->getWidth(), mLineHeight);
1542	return cell_rect;
1543}
1544
1545BOOL LLScrollListCtrl::handleToolTip(S32 x, S32 y, MASK mask)
1546{
1547	S32 column_index = getColumnIndexFromOffset(x);
1548	LLScrollListColumn* columnp = getColumn(column_index);
1549
1550	if (columnp == NULL) return FALSE;
1551
1552	BOOL handled = FALSE;
1553	// show tooltip for full name of hovered item if it has been truncated
1554	LLScrollListItem* hit_item = hitItem(x, y);
1555	if (hit_item)
1556	{
1557		LLScrollListCell* hit_cell = hit_item->getColumn(column_index);
1558		if (!hit_cell) return FALSE;
1559		if (hit_cell 
1560			&& hit_cell->isText()
1561			&& hit_cell->needsToolTip())
1562		{
1563			S32 row_index = getItemIndex(hit_item);
1564			LLRect cell_rect = getCellRect(row_index, column_index);
1565			// Convert rect local to screen coordinates
1566			LLRect sticky_rect;
1567			localRectToScreen(cell_rect, &sticky_rect);
1568
1569			// display tooltip exactly over original cell, in same font
1570			LLToolTipMgr::instance().show(LLToolTip::Params()
1571										.message(hit_cell->getToolTip())
1572										.font(LLFontGL::getFontSansSerifSmall())
1573										.pos(LLCoordGL(sticky_rect.mLeft - 5, sticky_rect.mTop + 6))
1574										.delay_time(0.2f)
1575										.sticky_rect(sticky_rect));		
1576		}
1577		handled = TRUE;
1578	}
1579
1580	// otherwise, look for a tooltip associated with this column
1581	LLScrollColumnHeader* headerp = columnp->mHeader;
1582	if (headerp && !handled)
1583	{
1584		handled = headerp->handleToolTip(x, y, mask);
1585	}
1586
1587	return handled;
1588}
1589
1590BOOL LLScrollListCtrl::selectItemAt(S32 x, S32 y, MASK mask)
1591{
1592	if (!mCanSelect) return FALSE;
1593
1594	BOOL selection_changed = FALSE;
1595
1596	LLScrollListItem* hit_item = hitItem(x, y);
1597
1598	if( hit_item )
1599	{
1600		if( mAllowMultipleSelection )
1601		{
1602			if (mask & MASK_SHIFT)
1603			{
1604				if (mLastSelected == NULL)
1605				{
1606					selectItem(hit_item);
1607				}
1608				else
1609				{
1610					// Select everthing between mLastSelected and hit_item
1611					bool selecting = false;
1612					item_list::iterator itor;
1613					// If we multiselect backwards, we'll stomp on mLastSelected,
1614					// meaning that we never stop selecting until hitting max or
1615					// the end of the list.
1616					LLScrollListItem* lastSelected = mLastSelected;
1617					for (itor = mItemList.begin(); itor != mItemList.end(); ++itor)
1618					{
1619						if(mMaxSelectable > 0 && getAllSelected().size() >= mMaxSelectable)
1620						{
1621							if(mOnMaximumSelectCallback)
1622							{
1623								mOnMaximumSelectCallback();
1624							}
1625							break;
1626						}
1627						LLScrollListItem *item = *itor;
1628                        if (item == hit_item || item == lastSelected)
1629						{
1630							selectItem(item, FALSE);
1631							selecting = !selecting;
1632							if (hit_item == lastSelected)
1633							{
1634								// stop selecting now, since we just clicked on our last selected item
1635								selecting = FALSE;
1636							}
1637						}
1638						if (selecting)
1639						{
1640							selectItem(item, FALSE);
1641						}
1642					}
1643				}
1644			}
1645			else if (mask & MASK_CONTROL)
1646			{
1647				if (hit_item->getSelected())
1648				{
1649					deselectItem(hit_item);
1650				}
1651				else
1652				{
1653					if(!(mMaxSelectable > 0 && getAllSelected().size() >= mMaxSelectable))
1654					{
1655						selectItem(hit_item, FALSE);
1656					}
1657					else
1658					{
1659						if(mOnMaximumSelectCallback)
1660						{
1661							mOnMaximumSelectCallback();
1662						}
1663					}
1664				}
1665			}
1666			else
1667			{
1668				deselectAllItems(TRUE);
1669				selectItem(hit_item);
1670			}
1671		}
1672		else
1673		{
1674			selectItem(hit_item);
1675		}
1676
1677		selection_changed = mSelectionChanged;
1678		if (mCommitOnSelectionChange)
1679		{
1680			commitIfChanged();
1681		}
1682
1683		// clear search string on mouse operations
1684		mSearchString.clear();
1685	}
1686	else
1687	{
1688		//mLastSelected = NULL;
1689		//deselectAllItems(TRUE);
1690	}
1691
1692	return selection_changed;
1693}
1694
1695
1696BOOL LLScrollListCtrl::handleMouseDown(S32 x, S32 y, MASK mask)
1697{
1698	BOOL handled = childrenHandleMouseDown(x, y, mask) != NULL;
1699
1700	if( !handled )
1701	{
1702		// set keyboard focus first, in case click action wants to move focus elsewhere
1703		setFocus(TRUE);
1704
1705		// clear selection changed flag because user is starting a selection operation
1706		mSelectionChanged = FALSE;
1707
1708		handleClick(x, y, mask);
1709	}
1710
1711	return TRUE;
1712}
1713
1714BOOL LLScrollListCtrl::handleMouseUp(S32 x, S32 y, MASK mask)
1715{	
1716	if (hasMouseCapture())
1717	{
1718		// release mouse capture immediately so 
1719		// scroll to show selected logic will work
1720		gFocusMgr.setMouseCapture(NULL);
1721		if(mask == MASK_NONE)
1722		{
1723			selectItemAt(x, y, mask);
1724			mNeedsScroll = TRUE;
1725		}
1726	}
1727
1728	// always commit when mouse operation is completed inside list
1729	if (mItemListRect.pointInRect(x,y))
1730	{
1731		mDirty |= mSelectionChanged;
1732		mSelectionChanged = FALSE;
1733		onCommit();
1734	}
1735
1736	return LLUICtrl::handleMouseUp(x, y, mask);
1737}
1738
1739// virtual
1740BOOL LLScrollListCtrl::handleRightMouseDown(S32 x, S32 y, MASK mask)
1741{
1742	LLScrollListItem *item = hitItem(x, y);
1743	if (item)
1744	{
1745		// check to see if we have a UUID for this row
1746		std::string id = item->getValue().asString();
1747		LLUUID uuid(id);
1748		if (! uuid.isNull() && mContextMenuType != MENU_NONE)
1749		{
1750			// set up the callbacks for all of the avatar/group menu items
1751			// (N.B. callbacks don't take const refs as id is local scope)
1752			bool is_group = (mContextMenuType == MENU_GROUP);
1753			LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar;
1754			registrar.add("Url.Execute", boost::bind(&LLScrollListCtrl::showNameDetails, id, is_group));
1755			registrar.add("Url.CopyLabel", boost::bind(&LLScrollListCtrl::copyNameToClipboard, id, is_group));
1756			registrar.add("Url.CopyUrl", boost::bind(&LLScrollListCtrl::copySLURLToClipboard, id, is_group));
1757
1758			// create the context menu from the XUI file and display it
1759			std::string menu_name = is_group ? "menu_url_group.xml" : "menu_url_agent.xml";
1760			delete mPopupMenu;
1761			mPopupMenu = LLUICtrlFactory::getInstance()->createFromFile<LLContextMenu>(
1762				menu_name, LLMenuGL::sMenuContainer, LLMenuHolderGL::child_registry_t::instance());
1763			if (mPopupMenu)
1764			{
1765				mPopupMenu->show(x, y);
1766				LLMenuGL::showPopup(this, mPopupMenu, x, y);
1767				return TRUE;
1768			}
1769		}
1770	}
1771	return FALSE;
1772}
1773
1774void LLScrollListCtrl::showNameDetails(std::string id, bool is_group)
1775{
1776	// show the resident's profile or the group profile
1777	std::string sltype = is_group ? "group" : "agent";
1778	std::string slurl = "secondlife:///app/" + sltype + "/" + id + "/about";
1779	LLUrlAction::clickAction(slurl);
1780}
1781
1782void LLScrollListCtrl::copyNameToClipboard(std::string id, bool is_group)
1783{
1784	// copy the name of the avatar or group to the clipboard
1785	std::string name;
1786	if (is_group)
1787	{
1788		gCacheName->getGroupName(LLUUID(id), name);
1789	}
1790	else
1791	{
1792		gCacheName->getFullName(LLUUID(id), name);
1793	}
1794	LLUrlAction::copyURLToClipboard(name);
1795}
1796
1797void LLScrollListCtrl::copySLURLToClipboard(std::string id, bool is_group)
1798{
1799	// copy a SLURL for the avatar or group to the clipboard
1800	std::string sltype = is_group ? "group" : "agent";
1801	std::string slurl = "secondlife:///app/" + sltype + "/" + id + "/about";
1802	LLUrlAction::copyURLToClipboard(slurl);
1803}
1804
1805BOOL LLScrollListCtrl::handleDoubleClick(S32 x, S32 y, MASK mask)
1806{
1807	//BOOL handled = FALSE;
1808	BOOL handled = handleClick(x, y, mask);
1809
1810	if (!handled)
1811	{
1812		// Offer the click to the children, even if we aren't enabled
1813		// so the scroll bars will work.
1814		if (NULL == LLView::childrenHandleDoubleClick(x, y, mask))
1815		{
1816			// Run the callback only if an item is being double-clicked.
1817			if( mCanSelect && hitItem(x, y) && mOnDoubleClickCallback )
1818			{
1819				mOnDoubleClickCallback();
1820			}
1821		}
1822	}
1823
1824	return TRUE;
1825}
1826
1827BOOL LLScrollListCtrl::handleClick(S32 x, S32 y, MASK mask)
1828{
1829	// which row was clicked on?
1830	LLScrollListItem* hit_item = hitItem(x, y);
1831	if (!hit_item) return FALSE;
1832
1833	// get appropriate cell from that row
1834	S32 column_index = getColumnIndexFromOffset(x);
1835	LLScrollListCell* hit_cell = hit_item->getColumn(column_index);
1836	if (!hit_cell) return FALSE;
1837
1838	// if cell handled click directly (i.e. clicked on an embedded checkbox)
1839	if (hit_cell->handleClick())
1840	{
1841		// if item not currently selected, select it
1842		if (!hit_item->getSelected())
1843		{
1844			selectItemAt(x, y, mask);
1845			gFocusMgr.setMouseCapture(this);
1846			mNeedsScroll = TRUE;
1847		}
1848		
1849		// propagate state of cell to rest of selected column
1850		{
1851			// propagate value of this cell to other selected items
1852			// and commit the respective widgets
1853			LLSD item_value = hit_cell->getValue();
1854			for (item_list::iterator iter = mItemList.begin(); iter != mItemList.end(); iter++)
1855			{
1856				LLScrollListItem* item = *iter;
1857				if (item->getSelected())
1858				{
1859					LLScrollListCell* cellp = item->getColumn(column_index);
1860					cellp->setValue(item_value);
1861					cellp->onCommit();
1862				}
1863			}
1864			//FIXME: find a better way to signal cell changes
1865			onCommit();
1866		}
1867		// eat click (e.g. do not trigger double click callback)
1868		return TRUE;
1869	}
1870	else
1871	{
1872		// treat this as a normal single item selection
1873		selectItemAt(x, y, mask);
1874		gFocusMgr.setMouseCapture(this);
1875		mNeedsScroll = TRUE;
1876		// do not eat click (allow double click callback)
1877		return FALSE;
1878	}
1879}
1880
1881LLScrollListItem* LLScrollListCtrl::hitItem( S32 x, S32 y )
1882{
1883	// Excludes disabled items.
1884	LLScrollListItem* hit_item = NULL;
1885
1886	updateSort();
1887
1888	LLRect item_rect;
1889	item_rect.setLeftTopAndSize( 
1890		mItemListRect.mLeft,
1891		mItemListRect.mTop,
1892		mItemListRect.getWidth(),
1893		mLineHeight );
1894
1895	// allow for partial line at bottom
1896	S32 num_page_lines = getLinesPerPage();
1897
1898	S32 line = 0;
1899	item_list::iterator iter;
1900	for(iter = mItemList.begin(); iter != mItemList.end(); iter++)
1901	{
1902		LLScrollListItem* item  = *iter;
1903		if( mScrollLines <= line && line < mScrollLines + num_page_lines )
1904		{
1905			if( item->getEnabled() && item_rect.pointInRect( x, y ) )
1906			{
1907				hit_item = item;
1908				break;
1909			}
1910
1911			item_rect.translate(0, -mLineHeight);
1912		}
1913		line++;
1914	}
1915
1916	return hit_item;
1917}
1918
1919S32 LLScrollListCtrl::getColumnIndexFromOffset(S32 x)
1920{
1921	// which column did we hit?
1922	S32 left = 0;
1923	S32 right = 0;
1924	S32 width = 0;
1925	S32 column_index = 0;
1926
1927	ordered_columns_t::const_iterator iter = mColumnsIndexed.begin();
1928	ordered_columns_t::const_iterator end = mColumnsIndexed.end();
1929	for ( ; iter != end; ++iter)
1930	{
1931		width = (*iter)->getWidth() + mColumnPadding;
1932		right += width;
1933		if (left <= x && x < right )
1934		{
1935			break;
1936		}
1937		
1938		// set left for next column as right of current column
1939		left = right;
1940		column_index++;
1941	}
1942
1943	return llclamp(column_index, 0, getNumColumns() - 1);
1944}
1945
1946
1947S32 LLScrollListCtrl::getColumnOffsetFromIndex(S32 index)
1948{
1949	S32 column_offset = 0;
1950	ordered_columns_t::const_iterator iter = mColumnsIndexed.begin();
1951	ordered_columns_t::const_iterator end = mColumnsIndexed.end();
1952	for ( ; iter != end; ++iter)
1953	{
1954		if (index-- <= 0)
1955		{
1956			return column_offset;
1957		}
1958		column_offset += (*iter)->getWidth() + mColumnPadding;
1959	}
1960
1961	// when running off the end, return the rightmost pixel
1962	return mItemListRect.mRight;
1963}
1964
1965S32 LLScrollListCtrl::getRowOffsetFromIndex(S32 index)
1966{
1967	S32 row_bottom = (mItemListRect.mTop - ((index - mScrollLines + 1) * mLineHeight) );
1968	return row_bottom;
1969}
1970
1971
1972BOOL LLScrollListCtrl::handleHover(S32 x,S32 y,MASK mask)
1973{
1974	BOOL	handled = FALSE;
1975
1976	if (hasMouseCapture())
1977	{
1978		if(mask == MASK_NONE)
1979		{
1980			selectItemAt(x, y, mask);
1981			mNeedsScroll = TRUE;
1982		}
1983	}
1984	else 
1985	if (mCanSelect)
1986	{
1987		LLScrollListItem* item = hitItem(x, y);
1988		if (item)
1989		{
1990			mouseOverHighlightNthItem(getItemIndex(item));
1991		}
1992		else
1993		{
1994			mouseOverHighlightNthItem(-1);
1995		}
1996	}
1997
1998	handled = LLUICtrl::handleHover( x, y, mask );
1999
2000	return handled;
2001}
2002
2003void LLScrollListCtrl::onMouseLeave(S32 x, S32 y, MASK mask)
2004{
2005	// clear mouse highlight
2006	mouseOverHighlightNthItem(-1);
2007}
2008
2009BOOL LLScrollListCtrl::handleKeyHere(KEY key,MASK mask )
2010{
2011	BOOL handled = FALSE;
2012
2013	// not called from parent means we have keyboard focus or a child does
2014	if (mCanSelect) 
2015	{
2016		// Ignore capslock
2017		mask = mask;
2018
2019		if (mask == MASK_NONE)
2020		{
2021			switch(key)
2022			{
2023			case KEY_UP:
2024				if (mAllowKeyboardMovement || hasFocus())
2025				{
2026					// commit implicit in call
2027					selectPrevItem(FALSE);
2028					mNeedsScroll = TRUE;
2029					handled = TRUE;
2030				}
2031				break;
2032			case KEY_DOWN:
2033				if (mAllowKeyboardMovement || hasFocus())
2034				{
2035					// commit implicit in call
2036					selectNextItem(FALSE);
2037					mNeedsScroll = TRUE;
2038					handled = TRUE;
2039				}
2040				break;
2041			case KEY_PAGE_UP:
2042				if (mAllowKeyboardMovement || hasFocus())
2043				{
2044					selectNthItem(getFirstSelectedIndex() - (mScrollbar->getPageSize() - 1));
2045					mNeedsScroll = TRUE;
2046					if (mCommitOnKeyboardMovement
2047						&& !mCommitOnSelectionChange) 
2048					{
2049						onCommit();
2050					}
2051					handled = TRUE;
2052				}
2053				break;
2054			case KEY_PAGE_DOWN:
2055				if (mAllowKeyboardMovement || hasFocus())
2056				{
2057					selectNthItem(getFirstSelectedIndex() + (mScrollbar->getPageSize() - 1));
2058					mNeedsScroll = TRUE;
2059					if (mCommitOnKeyboardMovement
2060						&& !mCommitOnSelectionChange) 
2061					{
2062						onCommit();
2063					}
2064					handled = TRUE;
2065				}
2066				break;
2067			case KEY_HOME:
2068				if (mAllowKeyboardMovement || hasFocus())
2069				{
2070					selectFirstItem();
2071					mNeedsScroll = TRUE;
2072					if (mCommitOnKeyboardMovement
2073						&& !mCommitOnSelectionChange) 
2074					{
2075						onCommit();
2076					}
2077					handled = TRUE;
2078				}
2079				break;
2080			case KEY_END:
2081				if (mAllowKeyboardMovement || hasFocus())
2082				{
2083					selectNthItem(getItemCount() - 1);
2084					mNeedsScroll = TRUE;
2085					if (mCommitOnKeyboardMovement
2086						&& !mCommitOnSelectionChange) 
2087					{
2088						onCommit();
2089					}
2090					handled = TRUE;
2091				}
2092				break;
2093			case KEY_RETURN:
2094				// JC - Special case: Only claim to have handled it
2095				// if we're the special non-commit-on-mov…

Large files files are truncated, but you can click here to view the full file