PageRenderTime 108ms CodeModel.GetById 14ms app.highlight 84ms RepoModel.GetById 1ms app.codeStats 1ms

/indra/llui/llflatlistview.cpp

https://bitbucket.org/lindenlab/viewer-beta/
C++ | 1339 lines | 992 code | 207 blank | 140 comment | 251 complexity | 958ba6a0e9b76c1ebed4529788c86da9 MD5 | raw file
   1/** 
   2 * @file llflatlistview.cpp
   3 * @brief LLFlatListView base class and extension to support messages for several cases of an empty list.
   4 *
   5 * $LicenseInfo:firstyear=2009&license=viewerlgpl$
   6 * Second Life Viewer Source Code
   7 * Copyright (C) 2010, Linden Research, Inc.
   8 * 
   9 * This library is free software; you can redistribute it and/or
  10 * modify it under the terms of the GNU Lesser General Public
  11 * License as published by the Free Software Foundation;
  12 * version 2.1 of the License only.
  13 * 
  14 * This library is distributed in the hope that it will be useful,
  15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
  16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  17 * Lesser General Public License for more details.
  18 * 
  19 * You should have received a copy of the GNU Lesser General Public
  20 * License along with this library; if not, write to the Free Software
  21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
  22 * 
  23 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
  24 * $/LicenseInfo$
  25 */
  26
  27#include "linden_common.h"
  28
  29#include "llpanel.h"
  30#include "lltextbox.h"
  31
  32#include "llflatlistview.h"
  33
  34static const LLDefaultChildRegistry::Register<LLFlatListView> flat_list_view("flat_list_view");
  35
  36const LLSD SELECTED_EVENT	= LLSD().with("selected", true);
  37const LLSD UNSELECTED_EVENT	= LLSD().with("selected", false);
  38
  39//forward declaration
  40bool llsds_are_equal(const LLSD& llsd_1, const LLSD& llsd_2);
  41
  42LLFlatListView::Params::Params()
  43:	item_pad("item_pad"),
  44	allow_select("allow_select"),
  45	multi_select("multi_select"),
  46	keep_one_selected("keep_one_selected"),
  47	keep_selection_visible_on_reshape("keep_selection_visible_on_reshape",false),
  48	no_items_text("no_items_text")
  49{};
  50
  51void LLFlatListView::reshape(S32 width, S32 height, BOOL called_from_parent /* = TRUE */)
  52{
  53	S32 delta = height - getRect().getHeight();
  54	LLScrollContainer::reshape(width, height, called_from_parent);
  55	setItemsNoScrollWidth(width);
  56	rearrangeItems();
  57	
  58	if(delta!= 0 && mKeepSelectionVisibleOnReshape)
  59	{
  60		ensureSelectedVisible();
  61	}
  62}
  63
  64const LLRect& LLFlatListView::getItemsRect() const
  65{
  66	return mItemsPanel->getRect(); 
  67}
  68
  69bool LLFlatListView::addItem(LLPanel * item, const LLSD& value /*= LLUUID::null*/, EAddPosition pos /*= ADD_BOTTOM*/,bool rearrange /*= true*/)
  70{
  71	if (!item) return false;
  72	if (value.isUndefined()) return false;
  73	
  74	//force uniqueness of items, easiest check but unreliable
  75	if (item->getParent() == mItemsPanel) return false;
  76	
  77	item_pair_t* new_pair = new item_pair_t(item, value);
  78	switch (pos)
  79	{
  80	case ADD_TOP:
  81		mItemPairs.push_front(new_pair);
  82		//in LLView::draw() children are iterated in backorder
  83		mItemsPanel->addChildInBack(item);
  84		break;
  85	case ADD_BOTTOM:
  86		mItemPairs.push_back(new_pair);
  87		mItemsPanel->addChild(item);
  88		break;
  89	default:
  90		break;
  91	}
  92	
  93	//_4 is for MASK
  94	item->setMouseDownCallback(boost::bind(&LLFlatListView::onItemMouseClick, this, new_pair, _4));
  95	item->setRightMouseDownCallback(boost::bind(&LLFlatListView::onItemRightMouseClick, this, new_pair, _4));
  96
  97	// Children don't accept the focus
  98	item->setTabStop(false);
  99
 100	if (rearrange)
 101	{
 102		rearrangeItems();
 103		notifyParentItemsRectChanged();
 104	}
 105	return true;
 106}
 107
 108
 109bool LLFlatListView::insertItemAfter(LLPanel* after_item, LLPanel* item_to_add, const LLSD& value /*= LLUUID::null*/)
 110{
 111	if (!after_item) return false;
 112	if (!item_to_add) return false;
 113	if (value.isUndefined()) return false;
 114
 115	if (mItemPairs.empty()) return false;
 116
 117	//force uniqueness of items, easiest check but unreliable
 118	if (item_to_add->getParent() == mItemsPanel) return false;
 119
 120	item_pair_t* after_pair = getItemPair(after_item);
 121	if (!after_pair) return false;
 122
 123	item_pair_t* new_pair = new item_pair_t(item_to_add, value);
 124	if (after_pair == mItemPairs.back())
 125	{
 126		mItemPairs.push_back(new_pair);
 127		mItemsPanel->addChild(item_to_add);
 128	}
 129	else
 130	{
 131		pairs_iterator_t it = mItemPairs.begin();
 132		for (; it != mItemPairs.end(); ++it)
 133		{
 134			if (*it == after_pair)
 135			{
 136				// insert new elements before the element at position of passed iterator.
 137				mItemPairs.insert(++it, new_pair);
 138				mItemsPanel->addChild(item_to_add);
 139				break;
 140			}
 141		}
 142	}
 143
 144	//_4 is for MASK
 145	item_to_add->setMouseDownCallback(boost::bind(&LLFlatListView::onItemMouseClick, this, new_pair, _4));
 146	item_to_add->setRightMouseDownCallback(boost::bind(&LLFlatListView::onItemRightMouseClick, this, new_pair, _4));
 147
 148	rearrangeItems();
 149	notifyParentItemsRectChanged();
 150	return true;
 151}
 152
 153
 154bool LLFlatListView::removeItem(LLPanel* item, bool rearrange)
 155{
 156	if (!item) return false;
 157	if (item->getParent() != mItemsPanel) return false;
 158	
 159	item_pair_t* item_pair = getItemPair(item);
 160	if (!item_pair) return false;
 161
 162	return removeItemPair(item_pair, rearrange);
 163}
 164
 165bool LLFlatListView::removeItemByValue(const LLSD& value, bool rearrange)
 166{
 167	if (value.isUndefined()) return false;
 168	
 169	item_pair_t* item_pair = getItemPair(value);
 170	if (!item_pair) return false;
 171
 172	return removeItemPair(item_pair, rearrange);
 173}
 174
 175bool LLFlatListView::removeItemByUUID(const LLUUID& uuid, bool rearrange)
 176{
 177	return removeItemByValue(LLSD(uuid), rearrange);
 178}
 179
 180LLPanel* LLFlatListView::getItemByValue(const LLSD& value) const
 181{
 182	if (value.isUndefined()) return NULL;
 183
 184	item_pair_t* pair = getItemPair(value);
 185	if (pair) return pair->first;
 186	return NULL;
 187}
 188
 189bool LLFlatListView::selectItem(LLPanel* item, bool select /*= true*/)
 190{
 191	if (!item) return false;
 192	if (item->getParent() != mItemsPanel) return false;
 193	
 194	item_pair_t* item_pair = getItemPair(item);
 195	if (!item_pair) return false;
 196
 197	return selectItemPair(item_pair, select);
 198}
 199
 200bool LLFlatListView::selectItemByValue(const LLSD& value, bool select /*= true*/)
 201{
 202	if (value.isUndefined()) return false;
 203
 204	item_pair_t* item_pair = getItemPair(value);
 205	if (!item_pair) return false;
 206
 207	return selectItemPair(item_pair, select);
 208}
 209
 210bool LLFlatListView::selectItemByUUID(const LLUUID& uuid, bool select /* = true*/)
 211{
 212	return selectItemByValue(LLSD(uuid), select);
 213}
 214
 215
 216LLSD LLFlatListView::getSelectedValue() const
 217{
 218	if (mSelectedItemPairs.empty()) return LLSD();
 219
 220	item_pair_t* first_selected_pair = mSelectedItemPairs.front();
 221	return first_selected_pair->second;
 222}
 223
 224void LLFlatListView::getSelectedValues(std::vector<LLSD>& selected_values) const
 225{
 226	if (mSelectedItemPairs.empty()) return;
 227
 228	for (pairs_const_iterator_t it = mSelectedItemPairs.begin(); it != mSelectedItemPairs.end(); ++it)
 229	{
 230		selected_values.push_back((*it)->second);
 231	}
 232}
 233
 234LLUUID LLFlatListView::getSelectedUUID() const
 235{
 236	const LLSD& value = getSelectedValue();
 237	if (value.isDefined() && value.isUUID())
 238	{
 239		return value.asUUID();
 240	}
 241	else 
 242	{
 243		return LLUUID::null;
 244	}
 245}
 246
 247void LLFlatListView::getSelectedUUIDs(uuid_vec_t& selected_uuids) const
 248{
 249	if (mSelectedItemPairs.empty()) return;
 250
 251	for (pairs_const_iterator_t it = mSelectedItemPairs.begin(); it != mSelectedItemPairs.end(); ++it)
 252	{
 253		selected_uuids.push_back((*it)->second.asUUID());
 254	}
 255}
 256
 257LLPanel* LLFlatListView::getSelectedItem() const
 258{
 259	if (mSelectedItemPairs.empty()) return NULL;
 260
 261	return mSelectedItemPairs.front()->first;
 262}
 263
 264void LLFlatListView::getSelectedItems(std::vector<LLPanel*>& selected_items) const
 265{
 266	if (mSelectedItemPairs.empty()) return;
 267
 268	for (pairs_const_iterator_t it = mSelectedItemPairs.begin(); it != mSelectedItemPairs.end(); ++it)
 269	{
 270		selected_items.push_back((*it)->first);
 271	}
 272}
 273
 274void LLFlatListView::resetSelection(bool no_commit_on_deselection /*= false*/)
 275{
 276	if (mSelectedItemPairs.empty()) return;
 277
 278	for (pairs_iterator_t it= mSelectedItemPairs.begin(); it != mSelectedItemPairs.end(); ++it)
 279	{
 280		item_pair_t* pair_to_deselect = *it;
 281		LLPanel* item = pair_to_deselect->first;
 282		item->setValue(UNSELECTED_EVENT);
 283	}
 284
 285	mSelectedItemPairs.clear();
 286
 287	if (mCommitOnSelectionChange && !no_commit_on_deselection)
 288	{
 289		onCommit();
 290	}
 291
 292	// Stretch selected item rect to ensure it won't be clipped
 293	mSelectedItemsBorder->setRect(getLastSelectedItemRect().stretch(-1));
 294}
 295
 296void LLFlatListView::setNoItemsCommentText(const std::string& comment_text)
 297{
 298	mNoItemsCommentTextbox->setValue(comment_text);
 299}
 300
 301U32 LLFlatListView::size(const bool only_visible_items) const
 302{
 303	if (only_visible_items)
 304	{
 305		U32 size = 0;
 306		for (pairs_const_iterator_t
 307				 iter = mItemPairs.begin(),
 308				 iter_end = mItemPairs.end();
 309			 iter != iter_end; ++iter)
 310		{
 311			if ((*iter)->first->getVisible())
 312				++size;
 313		}
 314		return size;
 315	}
 316	else
 317	{
 318		return mItemPairs.size();
 319	}
 320}
 321
 322void LLFlatListView::clear()
 323{
 324	// This will clear mSelectedItemPairs, calling all appropriate callbacks.
 325	resetSelection();
 326	
 327	// do not use LLView::deleteAllChildren to avoid removing nonvisible items. drag-n-drop for ex.
 328	for (pairs_iterator_t it = mItemPairs.begin(); it != mItemPairs.end(); ++it)
 329	{
 330		mItemsPanel->removeChild((*it)->first);
 331		(*it)->first->die();
 332		delete *it;
 333	}
 334	mItemPairs.clear();
 335
 336	// also set items panel height to zero. Reshape it to allow reshaping of non-item children
 337	LLRect rc = mItemsPanel->getRect();
 338	rc.mBottom = rc.mTop;
 339	mItemsPanel->reshape(rc.getWidth(), rc.getHeight());
 340	mItemsPanel->setRect(rc);
 341
 342	setNoItemsCommentVisible(true);
 343	notifyParentItemsRectChanged();
 344}
 345
 346void LLFlatListView::sort()
 347{
 348	if (!mItemComparator)
 349	{
 350		llwarns << "No comparator specified for sorting FlatListView items." << llendl;
 351		return;
 352	}
 353
 354	mItemPairs.sort(ComparatorAdaptor(*mItemComparator));
 355	rearrangeItems();
 356}
 357
 358bool LLFlatListView::updateValue(const LLSD& old_value, const LLSD& new_value)
 359{
 360	if (old_value.isUndefined() || new_value.isUndefined()) return false;
 361	if (llsds_are_equal(old_value, new_value)) return false;
 362
 363	item_pair_t* item_pair = getItemPair(old_value);
 364	if (!item_pair) return false;
 365
 366	item_pair->second = new_value;
 367	return true;
 368}
 369
 370//////////////////////////////////////////////////////////////////////////
 371// PROTECTED STUFF
 372//////////////////////////////////////////////////////////////////////////
 373
 374LLFlatListView::LLFlatListView(const LLFlatListView::Params& p)
 375:	LLScrollContainer(p)
 376  , mItemComparator(NULL)
 377  , mItemsPanel(NULL)
 378  , mItemPad(p.item_pad)
 379  , mAllowSelection(p.allow_select)
 380  , mMultipleSelection(p.multi_select)
 381  , mKeepOneItemSelected(p.keep_one_selected)
 382  , mCommitOnSelectionChange(false)
 383  , mPrevNotifyParentRect(LLRect())
 384  , mNoItemsCommentTextbox(NULL)
 385  , mIsConsecutiveSelection(false)
 386  , mKeepSelectionVisibleOnReshape(p.keep_selection_visible_on_reshape)
 387{
 388	mBorderThickness = getBorderWidth();
 389
 390	LLRect scroll_rect = getRect();
 391	LLRect items_rect;
 392
 393	setItemsNoScrollWidth(scroll_rect.getWidth());
 394	items_rect.setLeftTopAndSize(mBorderThickness, scroll_rect.getHeight() - mBorderThickness, mItemsNoScrollWidth, 0);
 395
 396	LLPanel::Params pp;
 397	pp.rect(items_rect);
 398	mItemsPanel = LLUICtrlFactory::create<LLPanel> (pp);
 399	addChild(mItemsPanel);
 400
 401	//we don't need to stretch in vertical direction on reshaping by a parent
 402	//no bottom following!
 403	mItemsPanel->setFollows(FOLLOWS_LEFT | FOLLOWS_RIGHT | FOLLOWS_TOP);
 404
 405	LLViewBorder::Params params;
 406	params.name("scroll border");
 407	params.rect(getLastSelectedItemRect());
 408	params.visible(false);
 409	params.bevel_style(LLViewBorder::BEVEL_IN);
 410	mSelectedItemsBorder = LLUICtrlFactory::create<LLViewBorder> (params);
 411	mItemsPanel->addChild( mSelectedItemsBorder );
 412
 413	{
 414		// create textbox for "No Items" comment text
 415		LLTextBox::Params text_p = p.no_items_text;
 416		if (!text_p.rect.isProvided())
 417		{
 418			LLRect comment_rect = getRect();
 419			comment_rect.setOriginAndSize(0, 0, comment_rect.getWidth(), comment_rect.getHeight());
 420			comment_rect.stretch(-getBorderWidth());
 421			text_p.rect(comment_rect);
 422		}
 423		text_p.border_visible(false);
 424
 425		if (!text_p.follows.isProvided())
 426		{
 427			text_p.follows.flags(FOLLOWS_ALL);
 428		}
 429		mNoItemsCommentTextbox = LLUICtrlFactory::create<LLTextBox>(text_p, this);
 430	}
 431};
 432
 433// virtual
 434void LLFlatListView::draw()
 435{
 436	// Highlight border if a child of this container has keyboard focus
 437	if( mSelectedItemsBorder->getVisible() )
 438	{
 439		mSelectedItemsBorder->setKeyboardFocusHighlight( hasFocus() );
 440	}
 441	LLScrollContainer::draw();
 442}
 443
 444// virtual
 445BOOL LLFlatListView::postBuild()
 446{
 447	setTabStop(true);
 448	return LLScrollContainer::postBuild();
 449}
 450
 451void LLFlatListView::rearrangeItems()
 452{
 453	static LLUICachedControl<S32> scrollbar_size ("UIScrollbarSize", 0);
 454
 455	setNoItemsCommentVisible(0==size());
 456
 457	if (mItemPairs.empty()) return;
 458
 459	//calculating required height - assuming items can be of different height
 460	//list should accommodate all its items
 461	S32 height = 0;
 462
 463	S32 invisible_children_count = 0;
 464	pairs_iterator_t it = mItemPairs.begin();
 465	for (; it != mItemPairs.end(); ++it)
 466	{
 467		LLPanel* item = (*it)->first;
 468
 469		// skip invisible child
 470		if (!item->getVisible())
 471		{
 472			++invisible_children_count;
 473			continue;
 474		}
 475
 476		height += item->getRect().getHeight();
 477	}
 478
 479	// add paddings between items, excluding invisible ones
 480	height += mItemPad * (mItemPairs.size() - invisible_children_count - 1);
 481
 482	LLRect rc = mItemsPanel->getRect();
 483	S32 width = mItemsNoScrollWidth;
 484
 485	// update width to avoid horizontal scrollbar
 486	if (height > getRect().getHeight() - 2 * mBorderThickness)
 487		width -= scrollbar_size;
 488
 489	//changes the bottom, end of the list goes down in the scroll container
 490	rc.setLeftTopAndSize(rc.mLeft, rc.mTop, width, height);
 491	mItemsPanel->setRect(rc);
 492
 493	//reshaping items
 494	S32 item_new_top = height;
 495	pairs_iterator_t it2, first_it = mItemPairs.begin();
 496	for (it2 = first_it; it2 != mItemPairs.end(); ++it2)
 497	{
 498		LLPanel* item = (*it2)->first;
 499
 500		// skip invisible child
 501		if (!item->getVisible())
 502			continue;
 503
 504		LLRect rc = item->getRect();
 505		rc.setLeftTopAndSize(rc.mLeft, item_new_top, width, rc.getHeight());
 506		item->reshape(rc.getWidth(), rc.getHeight());
 507		item->setRect(rc);
 508
 509		// move top for next item in list
 510		item_new_top -= (rc.getHeight() + mItemPad);
 511	}
 512
 513	// Stretch selected item rect to ensure it won't be clipped
 514	mSelectedItemsBorder->setRect(getLastSelectedItemRect().stretch(-1));
 515}
 516
 517void LLFlatListView::onItemMouseClick(item_pair_t* item_pair, MASK mask)
 518{
 519	if (!item_pair) return;
 520
 521	if (!item_pair->first) 
 522	{
 523		llwarning("Attempt to selet an item pair containing null panel item", 0);
 524		return;
 525	}
 526
 527	setFocus(TRUE);
 528	
 529	bool select_item = !isSelected(item_pair);
 530
 531	//*TODO find a better place for that enforcing stuff
 532	if (mKeepOneItemSelected && numSelected() == 1 && !select_item) return;
 533
 534	if ( (mask & MASK_SHIFT) && !(mask & MASK_CONTROL)
 535		 && mMultipleSelection && !mSelectedItemPairs.empty() )
 536	{
 537		item_pair_t* last_selected_pair = mSelectedItemPairs.back();
 538
 539		// If item_pair is already selected - do nothing
 540		if (last_selected_pair == item_pair)
 541			return;
 542
 543		bool grab_items = false;
 544		bool reverse = false;
 545		pairs_list_t pairs_to_select;
 546
 547		// Pick out items from list between last selected and current clicked item_pair.
 548		for (pairs_iterator_t
 549				 iter = mItemPairs.begin(),
 550				 iter_end = mItemPairs.end();
 551			 iter != iter_end; ++iter)
 552		{
 553			item_pair_t* cur = *iter;
 554			if (cur == last_selected_pair || cur == item_pair)
 555			{
 556				// We've got reverse selection if last grabed item isn't a new selection.
 557				reverse = grab_items && (cur != item_pair);
 558				grab_items = !grab_items;
 559				// Skip last selected and current clicked item pairs.
 560				continue;
 561			}
 562			if (!cur->first->getVisible())
 563			{
 564				// Skip invisible item pairs.
 565				continue;
 566			}
 567			if (grab_items)
 568			{
 569				pairs_to_select.push_back(cur);
 570			}
 571		}
 572
 573		if (reverse)
 574		{
 575			pairs_to_select.reverse();
 576		}
 577
 578		pairs_to_select.push_back(item_pair);
 579
 580		for (pairs_iterator_t
 581				 iter = pairs_to_select.begin(),
 582				 iter_end = pairs_to_select.end();
 583			 iter != iter_end; ++iter)
 584		{
 585			item_pair_t* pair_to_select = *iter;
 586			if (isSelected(pair_to_select))
 587			{
 588				// Item was already selected but there is a need to keep order from last selected pair to new selection.
 589				// Do it here to prevent extra mCommitOnSelectionChange in selectItemPair().
 590				mSelectedItemPairs.remove(pair_to_select);
 591				mSelectedItemPairs.push_back(pair_to_select);
 592			}
 593			else
 594			{
 595				selectItemPair(pair_to_select, true);
 596			}
 597		}
 598
 599		if (!select_item)
 600		{
 601			// Update last selected item border.
 602			mSelectedItemsBorder->setRect(getLastSelectedItemRect().stretch(-1));
 603		}
 604		return;
 605	}
 606
 607	//no need to do additional commit on selection reset
 608	if (!(mask & MASK_CONTROL) || !mMultipleSelection) resetSelection(true);
 609
 610	//only CTRL usage allows to deselect an item, usual clicking on an item cannot deselect it
 611	if (mask & MASK_CONTROL)
 612	selectItemPair(item_pair, select_item);
 613	else
 614		selectItemPair(item_pair, true);
 615}
 616
 617void LLFlatListView::onItemRightMouseClick(item_pair_t* item_pair, MASK mask)
 618{
 619	if (!item_pair)
 620		return;
 621
 622	// Forbid deselecting of items on right mouse button click if mMultipleSelection flag is set on,
 623	// because some of derived classes may have context menu and selected items must be kept.
 624	if ( !(mask & MASK_CONTROL) && mMultipleSelection && isSelected(item_pair) )
 625		return;
 626
 627	// else got same behavior as at onItemMouseClick
 628	onItemMouseClick(item_pair, mask);
 629}
 630
 631BOOL LLFlatListView::handleKeyHere(KEY key, MASK mask)
 632{
 633	BOOL reset_selection = (mask != MASK_SHIFT);
 634	BOOL handled = FALSE;
 635	switch (key)
 636	{
 637		case KEY_RETURN:
 638		{
 639			if (mSelectedItemPairs.size() && mask == MASK_NONE)
 640			{
 641				mOnReturnSignal(this, getValue());
 642				handled = TRUE;
 643			}
 644			break;
 645		}
 646		case KEY_UP:
 647		{
 648			if ( !selectNextItemPair(true, reset_selection) && reset_selection)
 649			{
 650				// If case we are in accordion tab notify parent to go to the previous accordion
 651				if(notifyParent(LLSD().with("action","select_prev")) > 0 )//message was processed
 652					resetSelection();
 653			}
 654			break;
 655		}
 656		case KEY_DOWN:
 657		{
 658			if ( !selectNextItemPair(false, reset_selection) && reset_selection)
 659			{
 660				// If case we are in accordion tab notify parent to go to the next accordion
 661				if( notifyParent(LLSD().with("action","select_next")) > 0 ) //message was processed
 662					resetSelection();
 663			}
 664			break;
 665		}
 666		case KEY_ESCAPE:
 667		{
 668			if (mask == MASK_NONE)
 669			{
 670				setFocus(FALSE); // pass focus to the game area (EXT-8357)
 671			}
 672			break;
 673		}
 674		default:
 675			break;
 676	}
 677
 678	if ( ( key == KEY_UP || key == KEY_DOWN ) && mSelectedItemPairs.size() )
 679	{
 680		ensureSelectedVisible();
 681		/*
 682		LLRect visible_rc = getVisibleContentRect();
 683		LLRect selected_rc = getLastSelectedItemRect();
 684
 685		if ( !visible_rc.contains (selected_rc) )
 686		{
 687			// But scroll in Items panel coordinates
 688			scrollToShowRect(selected_rc);
 689		}
 690
 691		// In case we are in accordion tab notify parent to show selected rectangle
 692		LLRect screen_rc;
 693		localRectToScreen(selected_rc, &screen_rc);
 694		notifyParent(LLSD().with("scrollToShowRect",screen_rc.getValue()));*/
 695
 696		handled = TRUE;
 697	}
 698
 699	return handled ? handled : LLScrollContainer::handleKeyHere(key, mask);
 700}
 701
 702LLFlatListView::item_pair_t* LLFlatListView::getItemPair(LLPanel* item) const
 703{
 704	llassert(item);
 705
 706	for (pairs_const_iterator_t it= mItemPairs.begin(); it != mItemPairs.end(); ++it)
 707	{
 708		item_pair_t* item_pair = *it;
 709		if (item_pair->first == item) return item_pair;
 710	}
 711	return NULL;
 712}
 713
 714//compares two LLSD's
 715bool llsds_are_equal(const LLSD& llsd_1, const LLSD& llsd_2)
 716{
 717	llassert(llsd_1.isDefined());
 718	llassert(llsd_2.isDefined());
 719	
 720	if (llsd_1.type() != llsd_2.type()) return false;
 721
 722	if (!llsd_1.isMap())
 723	{
 724		if (llsd_1.isUUID()) return llsd_1.asUUID() == llsd_2.asUUID();
 725
 726		//assumptions that string representaion is enough for other types
 727		return llsd_1.asString() == llsd_2.asString();
 728	}
 729
 730	if (llsd_1.size() != llsd_2.size()) return false;
 731
 732	LLSD::map_const_iterator llsd_1_it = llsd_1.beginMap();
 733	LLSD::map_const_iterator llsd_2_it = llsd_2.beginMap();
 734	for (S32 i = 0; i < llsd_1.size(); ++i)
 735	{
 736		if ((*llsd_1_it).first != (*llsd_2_it).first) return false;
 737		if (!llsds_are_equal((*llsd_1_it).second, (*llsd_2_it).second)) return false;
 738		++llsd_1_it;
 739		++llsd_2_it;
 740	}
 741	return true;
 742}
 743
 744LLFlatListView::item_pair_t* LLFlatListView::getItemPair(const LLSD& value) const
 745{
 746	llassert(value.isDefined());
 747	
 748	for (pairs_const_iterator_t it= mItemPairs.begin(); it != mItemPairs.end(); ++it)
 749	{
 750		item_pair_t* item_pair = *it;
 751		if (llsds_are_equal(item_pair->second, value)) return item_pair;
 752	}
 753	return NULL;
 754}
 755
 756bool LLFlatListView::selectItemPair(item_pair_t* item_pair, bool select)
 757{
 758	llassert(item_pair);
 759
 760	if (!mAllowSelection && select) return false;
 761
 762	if (isSelected(item_pair) == select) return true; //already in specified selection state
 763	if (select)
 764	{
 765		mSelectedItemPairs.push_back(item_pair);
 766	}
 767	else
 768	{
 769		mSelectedItemPairs.remove(item_pair);
 770	}
 771
 772	//a way of notifying panel of selection state changes
 773	LLPanel* item = item_pair->first;
 774	item->setValue(select ? SELECTED_EVENT : UNSELECTED_EVENT);
 775
 776	if (mCommitOnSelectionChange)
 777	{
 778		onCommit();
 779	}
 780
 781	// Stretch selected item rect to ensure it won't be clipped
 782	mSelectedItemsBorder->setRect(getLastSelectedItemRect().stretch(-1));
 783	// By default mark it as not consecutive selection
 784	mIsConsecutiveSelection = false;
 785
 786	return true;
 787}
 788
 789void LLFlatListView::scrollToShowFirstSelectedItem()
 790{
 791	if (!mSelectedItemPairs.size())	return;
 792
 793	LLRect selected_rc = mSelectedItemPairs.front()->first->getRect();
 794
 795	if (selected_rc.isValid())
 796	{
 797		scrollToShowRect(selected_rc);
 798	}
 799}
 800
 801LLRect LLFlatListView::getLastSelectedItemRect()
 802{
 803	if (!mSelectedItemPairs.size())
 804	{
 805		return LLRect::null;
 806	}
 807
 808	return mSelectedItemPairs.back()->first->getRect();
 809}
 810
 811void LLFlatListView::selectFirstItem	()
 812{
 813	// No items - no actions!
 814	if (0 == size()) return;
 815
 816	// Select first visible item
 817	for (pairs_iterator_t
 818			 iter = mItemPairs.begin(),
 819			 iter_end = mItemPairs.end();
 820		 iter != iter_end; ++iter)
 821	{
 822		// skip invisible items
 823		if ( (*iter)->first->getVisible() )
 824		{
 825			selectItemPair(*iter, true);
 826			ensureSelectedVisible();
 827			break;
 828		}
 829	}
 830}
 831
 832void LLFlatListView::selectLastItem		()
 833{
 834	// No items - no actions!
 835	if (0 == size()) return;
 836
 837	// Select last visible item
 838	for (pairs_list_t::reverse_iterator
 839			 r_iter = mItemPairs.rbegin(),
 840			 r_iter_end = mItemPairs.rend();
 841		 r_iter != r_iter_end; ++r_iter)
 842	{
 843		// skip invisible items
 844		if ( (*r_iter)->first->getVisible() )
 845		{
 846			selectItemPair(*r_iter, true);
 847			ensureSelectedVisible();
 848			break;
 849		}
 850	}
 851}
 852
 853void LLFlatListView::ensureSelectedVisible()
 854{
 855	LLRect selected_rc = getLastSelectedItemRect();
 856
 857	if ( selected_rc.isValid() )
 858	{
 859		scrollToShowRect(selected_rc);
 860	}
 861}
 862
 863
 864// virtual
 865bool LLFlatListView::selectNextItemPair(bool is_up_direction, bool reset_selection)
 866{
 867	// No items - no actions!
 868	if ( 0 == size() )
 869		return false;
 870
 871	if (!mIsConsecutiveSelection)
 872	{
 873		// Leave only one item selected if list has not consecutive selection
 874		if (mSelectedItemPairs.size() && !reset_selection)
 875		{
 876			item_pair_t* cur_sel_pair = mSelectedItemPairs.back();
 877			resetSelection();
 878			selectItemPair (cur_sel_pair, true);
 879		}
 880	}
 881
 882	if ( mSelectedItemPairs.size() )
 883	{
 884		item_pair_t* to_sel_pair = NULL;
 885		item_pair_t* cur_sel_pair = NULL;
 886
 887		// Take the last selected pair
 888		cur_sel_pair = mSelectedItemPairs.back();
 889		// Bases on given direction choose next item to select
 890		if ( is_up_direction )
 891		{
 892			// Find current selected item position in mItemPairs list
 893			pairs_list_t::reverse_iterator sel_it = std::find(mItemPairs.rbegin(), mItemPairs.rend(), cur_sel_pair);
 894
 895			for (;++sel_it != mItemPairs.rend();)
 896			{
 897				// skip invisible items
 898				if ( (*sel_it)->first->getVisible() )
 899				{
 900					to_sel_pair = *sel_it;
 901					break;
 902				}
 903			}
 904		}
 905		else
 906		{
 907			// Find current selected item position in mItemPairs list
 908			pairs_list_t::iterator sel_it = std::find(mItemPairs.begin(), mItemPairs.end(), cur_sel_pair);
 909
 910			for (;++sel_it != mItemPairs.end();)
 911			{
 912				// skip invisible items
 913				if ( (*sel_it)->first->getVisible() )
 914				{
 915					to_sel_pair = *sel_it;
 916					break;
 917				}
 918			}
 919		}
 920
 921		if ( to_sel_pair )
 922		{
 923			bool select = true;
 924			if ( reset_selection )
 925			{
 926				// Reset current selection if we were asked about it
 927				resetSelection();
 928			}
 929			else
 930			{
 931				// If item already selected and no reset request than we should deselect last selected item.
 932				select = (mSelectedItemPairs.end() == std::find(mSelectedItemPairs.begin(), mSelectedItemPairs.end(), to_sel_pair));
 933			}
 934			// Select/Deselect next item
 935			selectItemPair(select ? to_sel_pair : cur_sel_pair, select);
 936			// Mark it as consecutive selection
 937			mIsConsecutiveSelection = true;
 938			return true;
 939		}
 940	}
 941	else
 942	{
 943		// If there weren't selected items then choose the first one bases on given direction
 944		// Force selection to first item
 945		if (is_up_direction)
 946			selectLastItem();
 947		else
 948			selectFirstItem();
 949		// Mark it as consecutive selection
 950		mIsConsecutiveSelection = true;
 951		return true;
 952	}
 953
 954	return false;
 955}
 956
 957BOOL LLFlatListView::canSelectAll() const
 958{
 959	return 0 != size() && mAllowSelection && mMultipleSelection;
 960}
 961
 962void LLFlatListView::selectAll()
 963{
 964	if (!mAllowSelection || !mMultipleSelection)
 965		return;
 966
 967	mSelectedItemPairs.clear();
 968
 969	for (pairs_const_iterator_t it= mItemPairs.begin(); it != mItemPairs.end(); ++it)
 970	{
 971		item_pair_t* item_pair = *it;
 972		mSelectedItemPairs.push_back(item_pair);
 973		//a way of notifying panel of selection state changes
 974		LLPanel* item = item_pair->first;
 975		item->setValue(SELECTED_EVENT);
 976	}
 977
 978	if (mCommitOnSelectionChange)
 979	{
 980		onCommit();
 981	}
 982
 983	// Stretch selected item rect to ensure it won't be clipped
 984	mSelectedItemsBorder->setRect(getLastSelectedItemRect().stretch(-1));
 985}
 986
 987bool LLFlatListView::isSelected(item_pair_t* item_pair) const
 988{
 989	llassert(item_pair);
 990
 991	pairs_const_iterator_t it_end = mSelectedItemPairs.end();
 992	return std::find(mSelectedItemPairs.begin(), it_end, item_pair) != it_end;
 993}
 994
 995bool LLFlatListView::removeItemPair(item_pair_t* item_pair, bool rearrange)
 996{
 997	llassert(item_pair);
 998
 999	bool deleted = false;
1000	bool selection_changed = false;
1001	for (pairs_iterator_t it = mItemPairs.begin(); it != mItemPairs.end(); ++it)
1002	{
1003		item_pair_t* _item_pair = *it;
1004		if (_item_pair == item_pair)
1005		{
1006			mItemPairs.erase(it);
1007			deleted = true;
1008			break;
1009		}
1010	}
1011
1012	if (!deleted) return false;
1013
1014	for (pairs_iterator_t it = mSelectedItemPairs.begin(); it != mSelectedItemPairs.end(); ++it)
1015	{
1016		item_pair_t* selected_item_pair = *it;
1017		if (selected_item_pair == item_pair)
1018		{
1019			it = mSelectedItemPairs.erase(it);
1020			selection_changed = true;
1021			break;
1022		}
1023	}
1024
1025	mItemsPanel->removeChild(item_pair->first);
1026	item_pair->first->die();
1027	delete item_pair;
1028
1029	if (rearrange)
1030	{
1031	rearrangeItems();
1032	notifyParentItemsRectChanged();
1033	}
1034
1035	if (selection_changed && mCommitOnSelectionChange)
1036	{
1037		onCommit();
1038	}
1039
1040	return true;
1041}
1042
1043void LLFlatListView::notifyParentItemsRectChanged()
1044{
1045	S32 comment_height = 0;
1046
1047	// take into account comment text height if exists
1048	if (mNoItemsCommentTextbox && mNoItemsCommentTextbox->getVisible())
1049	{
1050		// top text padding inside the textbox is included into the height
1051		comment_height = mNoItemsCommentTextbox->getTextPixelHeight();
1052
1053		// take into account a distance from parent's top border to textbox's top
1054		comment_height += getRect().getHeight() - mNoItemsCommentTextbox->getRect().mTop;
1055	}
1056
1057	LLRect req_rect =  getItemsRect();
1058
1059	// get maximum of items total height and comment text height
1060	req_rect.setOriginAndSize(req_rect.mLeft, req_rect.mBottom, req_rect.getWidth(), llmax(req_rect.getHeight(), comment_height));
1061
1062	// take into account border size.
1063	req_rect.stretch(getBorderWidth());
1064
1065	if (req_rect == mPrevNotifyParentRect)
1066		return;
1067
1068	mPrevNotifyParentRect = req_rect;
1069
1070	LLSD params;
1071	params["action"] = "size_changes";
1072	params["width"] = req_rect.getWidth();
1073	params["height"] = req_rect.getHeight();
1074
1075	if (getParent()) // dummy widgets don't have a parent
1076		getParent()->notifyParent(params);
1077}
1078
1079void LLFlatListView::setNoItemsCommentVisible(bool visible) const
1080{
1081	if (mNoItemsCommentTextbox)
1082	{
1083		mSelectedItemsBorder->setVisible(!visible);
1084		mNoItemsCommentTextbox->setVisible(visible);
1085	}
1086}
1087
1088void LLFlatListView::getItems(std::vector<LLPanel*>& items) const
1089{
1090	if (mItemPairs.empty()) return;
1091
1092	items.clear();
1093	for (pairs_const_iterator_t it = mItemPairs.begin(); it != mItemPairs.end(); ++it)
1094	{
1095		items.push_back((*it)->first);
1096	}
1097}
1098
1099void LLFlatListView::getValues(std::vector<LLSD>& values) const
1100{
1101	if (mItemPairs.empty()) return;
1102
1103	values.clear();
1104	for (pairs_const_iterator_t it = mItemPairs.begin(); it != mItemPairs.end(); ++it)
1105	{
1106		values.push_back((*it)->second);
1107	}
1108}
1109
1110// virtual
1111void LLFlatListView::onFocusReceived()
1112{
1113	if (size())
1114	{
1115	mSelectedItemsBorder->setVisible(TRUE);
1116	}
1117	gEditMenuHandler = this;
1118}
1119// virtual
1120void LLFlatListView::onFocusLost()
1121{
1122	mSelectedItemsBorder->setVisible(FALSE);
1123	// Route menu back to the default
1124 	if( gEditMenuHandler == this )
1125	{
1126		gEditMenuHandler = NULL;
1127	}
1128}
1129
1130//virtual 
1131S32 LLFlatListView::notify(const LLSD& info)
1132{
1133	if(info.has("action"))
1134	{
1135		std::string str_action = info["action"];
1136		if(str_action == "select_first")
1137		{
1138			setFocus(true);
1139			selectFirstItem();
1140			return 1;
1141		}
1142		else if(str_action == "select_last")
1143		{
1144			setFocus(true);
1145			selectLastItem();
1146			return 1;
1147		}
1148	}
1149	else if (info.has("rearrange"))
1150	{
1151		rearrangeItems();
1152		notifyParentItemsRectChanged();
1153		return 1;
1154	}
1155	return 0;
1156}
1157
1158void LLFlatListView::detachItems(std::vector<LLPanel*>& detached_items)
1159{
1160	LLSD action;
1161	action.with("detach", LLSD());
1162	// Clear detached_items list
1163	detached_items.clear();
1164	// Go through items and detach valid items, remove them from items panel
1165	// and add to detached_items.
1166	for (pairs_iterator_t
1167			 iter = mItemPairs.begin(),
1168			 iter_end = mItemPairs.end();
1169		 iter != iter_end; ++iter)
1170	{
1171		LLPanel* pItem = (*iter)->first;
1172		if (1 == pItem->notify(action))
1173		{
1174			selectItemPair((*iter), false);
1175			mItemsPanel->removeChild(pItem);
1176			detached_items.push_back(pItem);
1177		}
1178	}
1179	if (!detached_items.empty())
1180	{
1181		// Some items were detached, clean ourself from unusable memory
1182		if (detached_items.size() == mItemPairs.size())
1183		{
1184			// This way will be faster if all items were disconnected
1185			for (pairs_iterator_t
1186					 iter = mItemPairs.begin(),
1187					 iter_end = mItemPairs.end();
1188				 iter != iter_end; ++iter)
1189			{
1190				(*iter)->first = NULL;
1191				delete *iter;
1192			}
1193			mItemPairs.clear();
1194			// Also set items panel height to zero.
1195			// Reshape it to allow reshaping of non-item children.
1196			LLRect rc = mItemsPanel->getRect();
1197			rc.mBottom = rc.mTop;
1198			mItemsPanel->reshape(rc.getWidth(), rc.getHeight());
1199			mItemsPanel->setRect(rc);
1200			setNoItemsCommentVisible(true);
1201		}
1202		else
1203		{
1204			for (std::vector<LLPanel*>::const_iterator
1205					 detached_iter = detached_items.begin(),
1206					 detached_iter_end = detached_items.end();
1207				 detached_iter != detached_iter_end; ++detached_iter)
1208			{
1209				LLPanel* pDetachedItem = *detached_iter;
1210				for (pairs_iterator_t
1211						 iter = mItemPairs.begin(),
1212						 iter_end = mItemPairs.end();
1213					 iter != iter_end; ++iter)
1214				{
1215					item_pair_t* item_pair = *iter;
1216					if (item_pair->first == pDetachedItem)
1217					{
1218						mItemPairs.erase(iter);
1219						item_pair->first = NULL;
1220						delete item_pair;
1221						break;
1222					}
1223				}
1224			}
1225			rearrangeItems();
1226		}
1227		notifyParentItemsRectChanged();
1228	}
1229}
1230
1231
1232/************************************************************************/
1233/*             LLFlatListViewEx implementation                          */
1234/************************************************************************/
1235LLFlatListViewEx::Params::Params()
1236: no_items_msg("no_items_msg")
1237, no_filtered_items_msg("no_filtered_items_msg")
1238{
1239
1240}
1241
1242LLFlatListViewEx::LLFlatListViewEx(const Params& p)
1243:	LLFlatListView(p)
1244, mNoFilteredItemsMsg(p.no_filtered_items_msg)
1245, mNoItemsMsg(p.no_items_msg)
1246, mForceShowingUnmatchedItems(false)
1247, mHasMatchedItems(false)
1248{
1249
1250}
1251
1252void LLFlatListViewEx::updateNoItemsMessage(const std::string& filter_string)
1253{
1254	bool items_filtered = !filter_string.empty();
1255	if (items_filtered)
1256	{
1257		// items were filtered
1258		LLStringUtil::format_map_t args;
1259		args["[SEARCH_TERM]"] = LLURI::escape(filter_string);
1260		std::string text = mNoFilteredItemsMsg;
1261		LLStringUtil::format(text, args);
1262		setNoItemsCommentText(text);
1263	}
1264	else
1265	{
1266		// list does not contain any items at all
1267		setNoItemsCommentText(mNoItemsMsg);
1268	}
1269
1270}
1271
1272bool LLFlatListViewEx::getForceShowingUnmatchedItems()
1273{
1274	return mForceShowingUnmatchedItems;
1275}
1276
1277void LLFlatListViewEx::setForceShowingUnmatchedItems(bool show)
1278{
1279	mForceShowingUnmatchedItems = show;
1280}
1281
1282void LLFlatListViewEx::setFilterSubString(const std::string& filter_str)
1283{
1284	if (0 != LLStringUtil::compareInsensitive(filter_str, mFilterSubString))
1285	{
1286		mFilterSubString = filter_str;
1287		updateNoItemsMessage(mFilterSubString);
1288		filterItems();
1289	}
1290}
1291
1292void LLFlatListViewEx::filterItems()
1293{
1294	typedef std::vector <LLPanel*> item_panel_list_t;
1295
1296	std::string cur_filter = mFilterSubString;
1297	LLStringUtil::toUpper(cur_filter);
1298
1299	LLSD action;
1300	action.with("match_filter", cur_filter);
1301
1302	item_panel_list_t items;
1303	getItems(items);
1304
1305	mHasMatchedItems = false;
1306	for (item_panel_list_t::iterator
1307			 iter = items.begin(),
1308			 iter_end = items.end();
1309		 iter != iter_end; ++iter)
1310	{
1311		LLPanel* pItem = (*iter);
1312		// 0 signifies that filter is matched,
1313		// i.e. we don't hide items that don't support 'match_filter' action, separators etc.
1314		if (0 == pItem->notify(action))
1315		{
1316			mHasMatchedItems = true;
1317			pItem->setVisible(true);
1318		}
1319		else
1320		{
1321			// TODO: implement (re)storing of current selection.
1322			if(!mForceShowingUnmatchedItems)
1323			{
1324				selectItem(pItem, false);
1325			}
1326			pItem->setVisible(mForceShowingUnmatchedItems);
1327		}
1328	}
1329
1330	sort();
1331	notifyParentItemsRectChanged();
1332}
1333
1334bool LLFlatListViewEx::hasMatchedItems()
1335{
1336	return mHasMatchedItems;
1337}
1338
1339//EOF