PageRenderTime 134ms CodeModel.GetById 31ms app.highlight 95ms RepoModel.GetById 2ms app.codeStats 0ms

/indra/llui/llcombobox.cpp

https://bitbucket.org/lindenlab/viewer-beta/
C++ | 1112 lines | 851 code | 147 blank | 114 comment | 138 complexity | 4ffc4f7881f62b99d4c3e918e72e4ccc MD5 | raw file
   1/** 
   2 * @file llcombobox.cpp
   3 * @brief LLComboBox base class
   4 *
   5 * $LicenseInfo:firstyear=2001&license=viewerlgpl$
   6 * Second Life Viewer Source Code
   7 * Copyright (C) 2010, Linden Research, Inc.
   8 * 
   9 * This library is free software; you can redistribute it and/or
  10 * modify it under the terms of the GNU Lesser General Public
  11 * License as published by the Free Software Foundation;
  12 * version 2.1 of the License only.
  13 * 
  14 * This library is distributed in the hope that it will be useful,
  15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
  16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  17 * Lesser General Public License for more details.
  18 * 
  19 * You should have received a copy of the GNU Lesser General Public
  20 * License along with this library; if not, write to the Free Software
  21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
  22 * 
  23 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
  24 * $/LicenseInfo$
  25 */
  26
  27// A control that displays the name of the chosen item, which when
  28// clicked shows a scrolling box of options.
  29
  30#include "linden_common.h"
  31
  32// file includes
  33#include "llcombobox.h"
  34
  35// common includes
  36#include "llstring.h"
  37
  38// newview includes
  39#include "llbutton.h"
  40#include "llkeyboard.h"
  41#include "llscrolllistctrl.h"
  42#include "llwindow.h"
  43#include "llfloater.h"
  44#include "llscrollbar.h"
  45#include "llscrolllistcell.h"
  46#include "llscrolllistitem.h"
  47#include "llcontrol.h"
  48#include "llfocusmgr.h"
  49#include "lllineeditor.h"
  50#include "v2math.h"
  51#include "lluictrlfactory.h"
  52#include "lltooltip.h"
  53
  54// Globals
  55S32 MAX_COMBO_WIDTH = 500;
  56
  57static LLDefaultChildRegistry::Register<LLComboBox> register_combo_box("combo_box");
  58
  59void LLComboBox::PreferredPositionValues::declareValues()
  60{
  61	declare("above", ABOVE);
  62	declare("below", BELOW);
  63}
  64
  65LLComboBox::ItemParams::ItemParams()
  66:	label("label")
  67{
  68}
  69
  70
  71LLComboBox::Params::Params()
  72:	allow_text_entry("allow_text_entry", false),
  73	allow_new_values("allow_new_values", false),
  74	show_text_as_tentative("show_text_as_tentative", true),
  75	max_chars("max_chars", 20),
  76	list_position("list_position", BELOW),
  77	items("item"),
  78	combo_button("combo_button"),
  79	combo_list("combo_list"),
  80	combo_editor("combo_editor"),
  81	drop_down_button("drop_down_button")
  82{
  83	addSynonym(items, "combo_item");
  84}
  85
  86
  87LLComboBox::LLComboBox(const LLComboBox::Params& p)
  88:	LLUICtrl(p),
  89	mTextEntry(NULL),
  90	mTextEntryTentative(p.show_text_as_tentative),
  91	mHasAutocompletedText(false),
  92	mAllowTextEntry(p.allow_text_entry),
  93	mAllowNewValues(p.allow_new_values),
  94	mMaxChars(p.max_chars),
  95	mPrearrangeCallback(p.prearrange_callback()),
  96	mTextEntryCallback(p.text_entry_callback()),
  97	mTextChangedCallback(p.text_changed_callback()),
  98	mListPosition(p.list_position),
  99	mLastSelectedIndex(-1),
 100	mLabel(p.label)
 101{
 102	// Text label button
 103
 104	LLButton::Params button_params = (mAllowTextEntry ? p.combo_button : p.drop_down_button);
 105	button_params.mouse_down_callback.function(
 106		boost::bind(&LLComboBox::onButtonMouseDown, this));
 107	button_params.follows.flags(FOLLOWS_LEFT|FOLLOWS_BOTTOM|FOLLOWS_RIGHT);
 108	button_params.rect(p.rect);
 109
 110	if(mAllowTextEntry)
 111	{
 112		button_params.pad_right(2);
 113	}
 114
 115	mArrowImage = button_params.image_unselected;
 116
 117	mButton = LLUICtrlFactory::create<LLButton>(button_params);
 118
 119	
 120	if(mAllowTextEntry)
 121	{
 122		//redo to compensate for button hack that leaves space for a character
 123		//unless it is a "minimal combobox"(drop down)
 124		mButton->setRightHPad(2);
 125	}
 126	addChild(mButton);
 127
 128	LLScrollListCtrl::Params params = p.combo_list;
 129	params.name("ComboBox");
 130	params.commit_callback.function(boost::bind(&LLComboBox::onItemSelected, this, _2));
 131	params.visible(false);
 132	params.commit_on_keyboard_movement(false);
 133
 134	mList = LLUICtrlFactory::create<LLScrollListCtrl>(params);
 135	addChild(mList);
 136
 137	// Mouse-down on button will transfer mouse focus to the list
 138	// Grab the mouse-up event and make sure the button state is correct
 139	mList->setMouseUpCallback(boost::bind(&LLComboBox::onListMouseUp, this));
 140
 141	for (LLInitParam::ParamIterator<ItemParams>::const_iterator it = p.items.begin();
 142		it != p.items.end();
 143		++it)
 144	{
 145		LLScrollListItem::Params item_params = *it;
 146		if (it->label.isProvided())
 147		{
 148			item_params.columns.add().value(it->label());
 149		}
 150
 151		mList->addRow(item_params);
 152	}
 153
 154	createLineEditor(p);
 155
 156	mTopLostSignalConnection = setTopLostCallback(boost::bind(&LLComboBox::hideList, this));
 157}
 158
 159void LLComboBox::initFromParams(const LLComboBox::Params& p)
 160{
 161	LLUICtrl::initFromParams(p);
 162
 163	if (!acceptsTextInput() && mLabel.empty())
 164	{
 165		selectFirstItem();
 166	}
 167}
 168
 169// virtual
 170BOOL LLComboBox::postBuild()
 171{
 172	if (mControlVariable)
 173	{
 174		setValue(mControlVariable->getValue()); // selects the appropriate item
 175	}
 176	return TRUE;
 177}
 178
 179
 180LLComboBox::~LLComboBox()
 181{
 182	// children automatically deleted, including mMenu, mButton
 183
 184	// explicitly disconect this signal, since base class destructor might fire top lost
 185	mTopLostSignalConnection.disconnect();
 186}
 187
 188
 189void LLComboBox::clear()
 190{ 
 191	if (mTextEntry)
 192	{
 193		mTextEntry->setText(LLStringUtil::null);
 194	}
 195	mButton->setLabelSelected(LLStringUtil::null);
 196	mButton->setLabelUnselected(LLStringUtil::null);
 197	mList->deselectAllItems();
 198	mLastSelectedIndex = -1;
 199}
 200
 201void LLComboBox::onCommit()
 202{
 203	if (mAllowTextEntry && getCurrentIndex() != -1)
 204	{
 205		// we have selected an existing item, blitz the manual text entry with
 206		// the properly capitalized item
 207		mTextEntry->setValue(getSimple());
 208		mTextEntry->setTentative(FALSE);
 209	}
 210	setControlValue(getValue());
 211	LLUICtrl::onCommit();
 212}
 213
 214// virtual
 215BOOL LLComboBox::isDirty() const
 216{
 217	BOOL grubby = FALSE;
 218	if ( mList )
 219	{
 220		grubby = mList->isDirty();
 221	}
 222	return grubby;
 223}
 224
 225// virtual   Clear dirty state
 226void	LLComboBox::resetDirty()
 227{
 228	if ( mList )
 229	{
 230		mList->resetDirty();
 231	}
 232}
 233
 234bool LLComboBox::itemExists(const std::string& name)
 235{
 236	return mList->getItemByLabel(name);
 237}
 238
 239// add item "name" to menu
 240LLScrollListItem* LLComboBox::add(const std::string& name, EAddPosition pos, BOOL enabled)
 241{
 242	LLScrollListItem* item = mList->addSimpleElement(name, pos);
 243	item->setEnabled(enabled);
 244	if (!mAllowTextEntry && mLabel.empty())
 245	{
 246		selectFirstItem();
 247	}
 248	return item;
 249}
 250
 251// add item "name" with a unique id to menu
 252LLScrollListItem* LLComboBox::add(const std::string& name, const LLUUID& id, EAddPosition pos, BOOL enabled )
 253{
 254	LLScrollListItem* item = mList->addSimpleElement(name, pos, id);
 255	item->setEnabled(enabled);
 256	if (!mAllowTextEntry && mLabel.empty())
 257	{
 258		selectFirstItem();
 259	}
 260	return item;
 261}
 262
 263// add item "name" with attached userdata
 264LLScrollListItem* LLComboBox::add(const std::string& name, void* userdata, EAddPosition pos, BOOL enabled )
 265{
 266	LLScrollListItem* item = mList->addSimpleElement(name, pos);
 267	item->setEnabled(enabled);
 268	item->setUserdata( userdata );
 269	if (!mAllowTextEntry && mLabel.empty())
 270	{
 271		selectFirstItem();
 272	}
 273	return item;
 274}
 275
 276// add item "name" with attached generic data
 277LLScrollListItem* LLComboBox::add(const std::string& name, LLSD value, EAddPosition pos, BOOL enabled )
 278{
 279	LLScrollListItem* item = mList->addSimpleElement(name, pos, value);
 280	item->setEnabled(enabled);
 281	if (!mAllowTextEntry && mLabel.empty())
 282	{
 283		selectFirstItem();
 284	}
 285	return item;
 286}
 287
 288LLScrollListItem* LLComboBox::addSeparator(EAddPosition pos)
 289{
 290	return mList->addSeparator(pos);
 291}
 292
 293void LLComboBox::sortByName(BOOL ascending)
 294{
 295	mList->sortOnce(0, ascending);
 296}
 297
 298
 299// Choose an item with a given name in the menu.
 300// Returns TRUE if the item was found.
 301BOOL LLComboBox::setSimple(const LLStringExplicit& name)
 302{
 303	BOOL found = mList->selectItemByLabel(name, FALSE);
 304
 305	if (found)
 306	{
 307		setLabel(name);
 308		mLastSelectedIndex = mList->getFirstSelectedIndex();
 309	}
 310
 311	return found;
 312}
 313
 314// virtual
 315void LLComboBox::setValue(const LLSD& value)
 316{
 317	BOOL found = mList->selectByValue(value);
 318	if (found)
 319	{
 320		LLScrollListItem* item = mList->getFirstSelected();
 321		if (item)
 322		{
 323			updateLabel();
 324		}
 325		mLastSelectedIndex = mList->getFirstSelectedIndex();
 326	}
 327	else
 328	{
 329		mLastSelectedIndex = -1;
 330	}
 331}
 332
 333const std::string LLComboBox::getSimple() const
 334{
 335	const std::string res = getSelectedItemLabel();
 336	if (res.empty() && mAllowTextEntry)
 337	{
 338		return mTextEntry->getText();
 339	}
 340	else
 341	{
 342		return res;
 343	}
 344}
 345
 346const std::string LLComboBox::getSelectedItemLabel(S32 column) const
 347{
 348	return mList->getSelectedItemLabel(column);
 349}
 350
 351// virtual
 352LLSD LLComboBox::getValue() const
 353{
 354	LLScrollListItem* item = mList->getFirstSelected();
 355	if( item )
 356	{
 357		return item->getValue();
 358	}
 359	else if (mAllowTextEntry)
 360	{
 361		return mTextEntry->getValue();
 362	}
 363	else
 364	{
 365		return LLSD();
 366	}
 367}
 368
 369void LLComboBox::setLabel(const LLStringExplicit& name)
 370{
 371	if ( mTextEntry )
 372	{
 373		mTextEntry->setText(name);
 374		if (mList->selectItemByLabel(name, FALSE))
 375		{
 376			mTextEntry->setTentative(FALSE);
 377			mLastSelectedIndex = mList->getFirstSelectedIndex();
 378		}
 379		else
 380		{
 381			mTextEntry->setTentative(mTextEntryTentative);
 382		}
 383	}
 384	
 385	if (!mAllowTextEntry)
 386	{
 387		mButton->setLabel(name);
 388	}
 389}
 390
 391void LLComboBox::updateLabel()
 392{
 393	// Update the combo editor with the selected
 394	// item label.
 395	if (mTextEntry)
 396	{
 397		mTextEntry->setText(getSelectedItemLabel());
 398		mTextEntry->setTentative(FALSE);
 399	}
 400
 401	// If combo box doesn't allow text entry update
 402	// the combo button label.
 403	if (!mAllowTextEntry)
 404	{
 405		mButton->setLabel(getSelectedItemLabel());
 406	}
 407}
 408
 409BOOL LLComboBox::remove(const std::string& name)
 410{
 411	BOOL found = mList->selectItemByLabel(name);
 412
 413	if (found)
 414	{
 415		LLScrollListItem* item = mList->getFirstSelected();
 416		if (item)
 417		{
 418			mList->deleteSingleItem(mList->getItemIndex(item));
 419		}
 420		mLastSelectedIndex = mList->getFirstSelectedIndex();
 421	}
 422
 423	return found;
 424}
 425
 426BOOL LLComboBox::remove(S32 index)
 427{
 428	if (index < mList->getItemCount())
 429	{
 430		mList->deleteSingleItem(index);
 431		setLabel(getSelectedItemLabel());
 432		return TRUE;
 433	}
 434	return FALSE;
 435}
 436
 437// Keyboard focus lost.
 438void LLComboBox::onFocusLost()
 439{
 440	hideList();
 441	// if valid selection
 442	if (mAllowTextEntry && getCurrentIndex() != -1)
 443	{
 444		mTextEntry->selectAll();
 445	}
 446	LLUICtrl::onFocusLost();
 447}
 448
 449void LLComboBox::setButtonVisible(BOOL visible)
 450{
 451	static LLUICachedControl<S32> drop_shadow_button ("DropShadowButton", 0);
 452
 453	mButton->setVisible(visible);
 454	if (mTextEntry)
 455	{
 456		LLRect text_entry_rect(0, getRect().getHeight(), getRect().getWidth(), 0);
 457		if (visible)
 458		{
 459			S32 arrow_width = mArrowImage ? mArrowImage->getWidth() : 0;
 460			text_entry_rect.mRight -= llmax(8,arrow_width) + 2 * drop_shadow_button;
 461		}
 462		//mTextEntry->setRect(text_entry_rect);
 463		mTextEntry->reshape(text_entry_rect.getWidth(), text_entry_rect.getHeight(), TRUE);
 464	}
 465}
 466
 467BOOL LLComboBox::setCurrentByIndex( S32 index )
 468{
 469	BOOL found = mList->selectNthItem( index );
 470	if (found)
 471	{
 472		setLabel(getSelectedItemLabel());
 473		mLastSelectedIndex = index;
 474	}
 475	return found;
 476}
 477
 478S32 LLComboBox::getCurrentIndex() const
 479{
 480	LLScrollListItem* item = mList->getFirstSelected();
 481	if( item )
 482	{
 483		return mList->getItemIndex( item );
 484	}
 485	return -1;
 486}
 487
 488
 489void LLComboBox::createLineEditor(const LLComboBox::Params& p)
 490{
 491	static LLUICachedControl<S32> drop_shadow_button ("DropShadowButton", 0);
 492	LLRect rect = getLocalRect();
 493	if (mAllowTextEntry)
 494	{
 495		S32 arrow_width = mArrowImage ? mArrowImage->getWidth() : 0;
 496		S32 shadow_size = drop_shadow_button;
 497		mButton->setRect(LLRect( getRect().getWidth() - llmax(8,arrow_width) - 2 * shadow_size,
 498								rect.mTop, rect.mRight, rect.mBottom));
 499		mButton->setTabStop(FALSE);
 500		mButton->setHAlign(LLFontGL::HCENTER);
 501
 502		LLRect text_entry_rect(0, getRect().getHeight(), getRect().getWidth(), 0);
 503		text_entry_rect.mRight -= llmax(8,arrow_width) + 2 * drop_shadow_button;
 504		// clear label on button
 505		std::string cur_label = mButton->getLabelSelected();
 506		LLLineEditor::Params params = p.combo_editor;
 507		params.rect(text_entry_rect);
 508		params.default_text(LLStringUtil::null);
 509		params.max_length.bytes(mMaxChars);
 510		params.commit_callback.function(boost::bind(&LLComboBox::onTextCommit, this, _2));
 511		params.keystroke_callback(boost::bind(&LLComboBox::onTextEntry, this, _1));
 512		params.commit_on_focus_lost(false);
 513		params.follows.flags(FOLLOWS_ALL);
 514		params.label(mLabel);
 515		mTextEntry = LLUICtrlFactory::create<LLLineEditor> (params);
 516		mTextEntry->setText(cur_label);
 517		mTextEntry->setIgnoreTab(TRUE);
 518		addChild(mTextEntry);
 519
 520		// clear label on button
 521		setLabel(LLStringUtil::null);
 522
 523		mButton->setFollows(FOLLOWS_BOTTOM | FOLLOWS_TOP | FOLLOWS_RIGHT);
 524	}
 525	else
 526	{
 527		mButton->setRect(rect);
 528		mButton->setLabel(mLabel.getString());
 529		
 530		if (mTextEntry)
 531		{
 532			mTextEntry->setVisible(FALSE);
 533		}
 534	}
 535}
 536
 537void* LLComboBox::getCurrentUserdata()
 538{
 539	LLScrollListItem* item = mList->getFirstSelected();
 540	if( item )
 541	{
 542		return item->getUserdata();
 543	}
 544	return NULL;
 545}
 546
 547
 548void LLComboBox::showList()
 549{
 550	// Make sure we don't go off top of screen.
 551	LLCoordWindow window_size;
 552	getWindow()->getSize(&window_size);
 553	//HACK: shouldn't have to know about scale here
 554	mList->fitContents( 192, llfloor((F32)window_size.mY / LLUI::sGLScaleFactor.mV[VY]) - 50 );
 555
 556	// Make sure that we can see the whole list
 557	LLRect root_view_local;
 558	LLView* root_view = getRootView();
 559	root_view->localRectToOtherView(root_view->getLocalRect(), &root_view_local, this);
 560	
 561	LLRect rect = mList->getRect();
 562
 563	S32 min_width = getRect().getWidth();
 564	S32 max_width = llmax(min_width, MAX_COMBO_WIDTH);
 565	// make sure we have up to date content width metrics
 566	mList->calcColumnWidths();
 567	S32 list_width = llclamp(mList->getMaxContentWidth(), min_width, max_width);
 568
 569	if (mListPosition == BELOW)
 570	{
 571		if (rect.getHeight() <= -root_view_local.mBottom)
 572		{
 573			// Move rect so it hangs off the bottom of this view
 574			rect.setLeftTopAndSize(0, 0, list_width, rect.getHeight() );
 575		}
 576		else
 577		{	
 578			// stack on top or bottom, depending on which has more room
 579			if (-root_view_local.mBottom > root_view_local.mTop - getRect().getHeight())
 580			{
 581				// Move rect so it hangs off the bottom of this view
 582				rect.setLeftTopAndSize(0, 0, list_width, llmin(-root_view_local.mBottom, rect.getHeight()));
 583			}
 584			else
 585			{
 586				// move rect so it stacks on top of this view (clipped to size of screen)
 587				rect.setOriginAndSize(0, getRect().getHeight(), list_width, llmin(root_view_local.mTop - getRect().getHeight(), rect.getHeight()));
 588			}
 589		}
 590	}
 591	else // ABOVE
 592	{
 593		if (rect.getHeight() <= root_view_local.mTop - getRect().getHeight())
 594		{
 595			// move rect so it stacks on top of this view (clipped to size of screen)
 596			rect.setOriginAndSize(0, getRect().getHeight(), list_width, llmin(root_view_local.mTop - getRect().getHeight(), rect.getHeight()));
 597		}
 598		else
 599		{
 600			// stack on top or bottom, depending on which has more room
 601			if (-root_view_local.mBottom > root_view_local.mTop - getRect().getHeight())
 602			{
 603				// Move rect so it hangs off the bottom of this view
 604				rect.setLeftTopAndSize(0, 0, list_width, llmin(-root_view_local.mBottom, rect.getHeight()));
 605			}
 606			else
 607			{
 608				// move rect so it stacks on top of this view (clipped to size of screen)
 609				rect.setOriginAndSize(0, getRect().getHeight(), list_width, llmin(root_view_local.mTop - getRect().getHeight(), rect.getHeight()));
 610			}
 611		}
 612
 613	}
 614	mList->setOrigin(rect.mLeft, rect.mBottom);
 615	mList->reshape(rect.getWidth(), rect.getHeight());
 616	mList->translateIntoRect(root_view_local, FALSE);
 617
 618	// Make sure we didn't go off bottom of screen
 619	S32 x, y;
 620	mList->localPointToScreen(0, 0, &x, &y);
 621
 622	if (y < 0)
 623	{
 624		mList->translate(0, -y);
 625	}
 626
 627	// NB: this call will trigger the focuslost callback which will hide the list, so do it first
 628	// before finally showing the list
 629
 630	mList->setFocus(TRUE);
 631
 632	// Show the list and push the button down
 633	mButton->setToggleState(TRUE);
 634	mList->setVisible(TRUE);
 635	
 636	LLUI::addPopup(this);
 637
 638	setUseBoundingRect(TRUE);
 639//	updateBoundingRect();
 640}
 641
 642void LLComboBox::hideList()
 643{
 644	if (mList->getVisible())
 645	{
 646		// assert selection in list
 647		if(mAllowNewValues)
 648		{
 649			// mLastSelectedIndex = -1 means that we entered a new value, don't select
 650			// any of existing items in this case.
 651			if(mLastSelectedIndex >= 0)
 652				mList->selectNthItem(mLastSelectedIndex);
 653		}
 654		else if(mLastSelectedIndex >= 0)
 655			mList->selectNthItem(mLastSelectedIndex);
 656
 657		mButton->setToggleState(FALSE);
 658		mList->setVisible(FALSE);
 659		mList->mouseOverHighlightNthItem(-1);
 660
 661		setUseBoundingRect(FALSE);
 662		LLUI::removePopup(this);
 663//		updateBoundingRect();
 664	}
 665}
 666
 667void LLComboBox::onButtonMouseDown()
 668{
 669	if (!mList->getVisible())
 670	{
 671		// this might change selection, so do it first
 672		prearrangeList();
 673
 674		// highlight the last selected item from the original selection before potentially selecting a new item
 675		// as visual cue to original value of combo box
 676		LLScrollListItem* last_selected_item = mList->getLastSelectedItem();
 677		if (last_selected_item)
 678		{
 679			mList->mouseOverHighlightNthItem(mList->getItemIndex(last_selected_item));
 680		}
 681
 682		if (mList->getItemCount() != 0)
 683		{
 684			showList();
 685		}
 686
 687		setFocus( TRUE );
 688
 689		// pass mouse capture on to list if button is depressed
 690		if (mButton->hasMouseCapture())
 691		{
 692			gFocusMgr.setMouseCapture(mList);
 693
 694			// But keep the "pressed" look, which buttons normally lose when they
 695			// lose focus
 696			mButton->setForcePressedState(true);
 697		}
 698	}
 699	else
 700	{
 701		hideList();
 702	} 
 703
 704}
 705
 706void LLComboBox::onListMouseUp()
 707{
 708	// In some cases this is the termination of a mouse click that started on
 709	// the button, so clear its pressed state
 710	mButton->setForcePressedState(false);
 711}
 712
 713//------------------------------------------------------------------
 714// static functions
 715//------------------------------------------------------------------
 716
 717void LLComboBox::onItemSelected(const LLSD& data)
 718{
 719	mLastSelectedIndex = getCurrentIndex();
 720	if (mLastSelectedIndex != -1)
 721	{
 722		updateLabel();
 723
 724		if (mAllowTextEntry)
 725		{
 726			gFocusMgr.setKeyboardFocus(mTextEntry);
 727			mTextEntry->selectAll();
 728		}
 729	}
 730	// hiding the list reasserts the old value stored in the text editor/dropdown button
 731	hideList();
 732
 733	// commit does the reverse, asserting the value in the list
 734	onCommit();
 735}
 736
 737BOOL LLComboBox::handleToolTip(S32 x, S32 y, MASK mask)
 738{
 739    std::string tool_tip;
 740
 741	if(LLUICtrl::handleToolTip(x, y, mask))
 742	{
 743		return TRUE;
 744	}
 745	
 746	tool_tip = getToolTip();
 747	if (tool_tip.empty())
 748	{
 749		tool_tip = getSelectedItemLabel();
 750	}
 751	
 752	if( !tool_tip.empty() )
 753	{
 754		LLToolTipMgr::instance().show(LLToolTip::Params()
 755			.message(tool_tip)
 756			.sticky_rect(calcScreenRect()));
 757	}
 758	return TRUE;
 759}
 760
 761BOOL LLComboBox::handleKeyHere(KEY key, MASK mask)
 762{
 763	BOOL result = FALSE;
 764	if (hasFocus())
 765	{
 766		if (mList->getVisible() 
 767			&& key == KEY_ESCAPE && mask == MASK_NONE)
 768		{
 769			hideList();
 770			return TRUE;
 771		}
 772		//give list a chance to pop up and handle key
 773		LLScrollListItem* last_selected_item = mList->getLastSelectedItem();
 774		if (last_selected_item)
 775		{
 776			// highlight the original selection before potentially selecting a new item
 777			mList->mouseOverHighlightNthItem(mList->getItemIndex(last_selected_item));
 778		}
 779		result = mList->handleKeyHere(key, mask);
 780
 781		// will only see return key if it is originating from line editor
 782		// since the dropdown button eats the key
 783		if (key == KEY_RETURN)
 784		{
 785			// don't show list and don't eat key input when committing
 786			// free-form text entry with RETURN since user already knows
 787            // what they are trying to select
 788			return FALSE;
 789		}
 790		// if selection has changed, pop open list
 791		else if (mList->getLastSelectedItem() != last_selected_item 
 792					|| ((key == KEY_DOWN || key == KEY_UP)
 793						&& mList->getCanSelect()
 794						&& !mList->isEmpty()))
 795		{
 796			showList();
 797		}
 798	}
 799	return result;
 800}
 801
 802BOOL LLComboBox::handleUnicodeCharHere(llwchar uni_char)
 803{
 804	BOOL result = FALSE;
 805	if (gFocusMgr.childHasKeyboardFocus(this))
 806	{
 807		// space bar just shows the list
 808		if (' ' != uni_char )
 809		{
 810			LLScrollListItem* last_selected_item = mList->getLastSelectedItem();
 811			if (last_selected_item)
 812			{
 813				// highlight the original selection before potentially selecting a new item
 814				mList->mouseOverHighlightNthItem(mList->getItemIndex(last_selected_item));
 815			}
 816			result = mList->handleUnicodeCharHere(uni_char);
 817			if (mList->getLastSelectedItem() != last_selected_item)
 818			{
 819				showList();
 820			}
 821		}
 822	}
 823	return result;
 824}
 825
 826void LLComboBox::setTextEntry(const LLStringExplicit& text)
 827{
 828	if (mTextEntry)
 829	{
 830		mTextEntry->setText(text);
 831		mHasAutocompletedText = FALSE;
 832		updateSelection();
 833	}
 834}
 835
 836void LLComboBox::onTextEntry(LLLineEditor* line_editor)
 837{
 838	if (mTextEntryCallback != NULL)
 839	{
 840		(mTextEntryCallback)(line_editor, LLSD());
 841	}
 842
 843	KEY key = gKeyboard->currentKey();
 844	if (key == KEY_BACKSPACE || 
 845		key == KEY_DELETE)
 846	{
 847		if (mList->selectItemByLabel(line_editor->getText(), FALSE))
 848		{
 849			line_editor->setTentative(FALSE);
 850			mLastSelectedIndex = mList->getFirstSelectedIndex();
 851		}
 852		else
 853		{
 854			line_editor->setTentative(mTextEntryTentative);
 855			mList->deselectAllItems();
 856			mLastSelectedIndex = -1;
 857		}
 858		if (mTextChangedCallback != NULL)
 859		{
 860			(mTextChangedCallback)(line_editor, LLSD());
 861		}
 862		return;
 863	}
 864
 865	if (key == KEY_LEFT || 
 866		key == KEY_RIGHT)
 867	{
 868		return;
 869	}
 870
 871	if (key == KEY_DOWN)
 872	{
 873		setCurrentByIndex(llmin(getItemCount() - 1, getCurrentIndex() + 1));
 874		if (!mList->getVisible())
 875		{
 876			prearrangeList();
 877
 878			if (mList->getItemCount() != 0)
 879			{
 880				showList();
 881			}
 882		}
 883		line_editor->selectAll();
 884		line_editor->setTentative(FALSE);
 885	}
 886	else if (key == KEY_UP)
 887	{
 888		setCurrentByIndex(llmax(0, getCurrentIndex() - 1));
 889		if (!mList->getVisible())
 890		{
 891			prearrangeList();
 892
 893			if (mList->getItemCount() != 0)
 894			{
 895				showList();
 896			}
 897		}
 898		line_editor->selectAll();
 899		line_editor->setTentative(FALSE);
 900	}
 901	else
 902	{
 903		// RN: presumably text entry
 904		updateSelection();
 905	}
 906	if (mTextChangedCallback != NULL)
 907	{
 908		(mTextChangedCallback)(line_editor, LLSD());
 909	}
 910}
 911
 912void LLComboBox::updateSelection()
 913{
 914	LLWString left_wstring = mTextEntry->getWText().substr(0, mTextEntry->getCursor());
 915	// user-entered portion of string, based on assumption that any selected
 916    // text was a result of auto-completion
 917	LLWString user_wstring = mHasAutocompletedText ? left_wstring : mTextEntry->getWText();
 918	std::string full_string = mTextEntry->getText();
 919
 920	// go ahead and arrange drop down list on first typed character, even
 921	// though we aren't showing it... some code relies on prearrange
 922	// callback to populate content
 923	if( mTextEntry->getWText().size() == 1 )
 924	{
 925		prearrangeList(mTextEntry->getText());
 926	}
 927
 928	if (mList->selectItemByLabel(full_string, FALSE))
 929	{
 930		mTextEntry->setTentative(FALSE);
 931		mLastSelectedIndex = mList->getFirstSelectedIndex();
 932	}
 933	else if (mList->selectItemByPrefix(left_wstring, FALSE))
 934	{
 935		LLWString selected_item = utf8str_to_wstring(getSelectedItemLabel());
 936		LLWString wtext = left_wstring + selected_item.substr(left_wstring.size(), selected_item.size());
 937		mTextEntry->setText(wstring_to_utf8str(wtext));
 938		mTextEntry->setSelection(left_wstring.size(), mTextEntry->getWText().size());
 939		mTextEntry->endSelection();
 940		mTextEntry->setTentative(FALSE);
 941		mHasAutocompletedText = TRUE;
 942		mLastSelectedIndex = mList->getFirstSelectedIndex();
 943	}
 944	else // no matching items found
 945	{
 946		mList->deselectAllItems();
 947		mTextEntry->setText(wstring_to_utf8str(user_wstring)); // removes text added by autocompletion
 948		mTextEntry->setTentative(mTextEntryTentative);
 949		mHasAutocompletedText = FALSE;
 950		mLastSelectedIndex = -1;
 951	}
 952}
 953
 954void LLComboBox::onTextCommit(const LLSD& data)
 955{
 956	std::string text = mTextEntry->getText();
 957	setSimple(text);
 958	onCommit();
 959	mTextEntry->selectAll();
 960}
 961
 962void LLComboBox::setFocus(BOOL b)
 963{
 964	LLUICtrl::setFocus(b);
 965
 966	if (b)
 967	{
 968		mList->clearSearchString();
 969		if (mList->getVisible())
 970		{
 971			mList->setFocus(TRUE);
 972		}
 973	}
 974}
 975
 976void LLComboBox::prearrangeList(std::string filter)
 977{
 978	if (mPrearrangeCallback)
 979	{
 980		mPrearrangeCallback(this, LLSD(filter));
 981	}
 982}
 983
 984//============================================================================
 985// LLCtrlListInterface functions
 986
 987S32 LLComboBox::getItemCount() const
 988{
 989	return mList->getItemCount();
 990}
 991
 992void LLComboBox::addColumn(const LLSD& column, EAddPosition pos)
 993{
 994	mList->clearColumns();
 995	mList->addColumn(column, pos);
 996}
 997
 998void LLComboBox::clearColumns()
 999{
1000	mList->clearColumns();
1001}
1002
1003void LLComboBox::setColumnLabel(const std::string& column, const std::string& label)
1004{
1005	mList->setColumnLabel(column, label);
1006}
1007
1008LLScrollListItem* LLComboBox::addElement(const LLSD& value, EAddPosition pos, void* userdata)
1009{
1010	return mList->addElement(value, pos, userdata);
1011}
1012
1013LLScrollListItem* LLComboBox::addSimpleElement(const std::string& value, EAddPosition pos, const LLSD& id)
1014{
1015	return mList->addSimpleElement(value, pos, id);
1016}
1017
1018void LLComboBox::clearRows()
1019{
1020	mList->clearRows();
1021}
1022
1023void LLComboBox::sortByColumn(const std::string& name, BOOL ascending)
1024{
1025	mList->sortByColumn(name, ascending);
1026}
1027
1028//============================================================================
1029//LLCtrlSelectionInterface functions
1030
1031BOOL LLComboBox::setCurrentByID(const LLUUID& id)
1032{
1033	BOOL found = mList->selectByID( id );
1034
1035	if (found)
1036	{
1037		setLabel(getSelectedItemLabel());
1038		mLastSelectedIndex = mList->getFirstSelectedIndex();
1039	}
1040
1041	return found;
1042}
1043
1044LLUUID LLComboBox::getCurrentID() const
1045{
1046	return mList->getStringUUIDSelectedItem();
1047}
1048BOOL LLComboBox::setSelectedByValue(const LLSD& value, BOOL selected)
1049{
1050	BOOL found = mList->setSelectedByValue(value, selected);
1051	if (found)
1052	{
1053		setLabel(getSelectedItemLabel());
1054	}
1055	return found;
1056}
1057
1058LLSD LLComboBox::getSelectedValue()
1059{
1060	return mList->getSelectedValue();
1061}
1062
1063BOOL LLComboBox::isSelected(const LLSD& value) const
1064{
1065	return mList->isSelected(value);
1066}
1067
1068BOOL LLComboBox::operateOnSelection(EOperation op)
1069{
1070	if (op == OP_DELETE)
1071	{
1072		mList->deleteSelectedItems();
1073		return TRUE;
1074	}
1075	return FALSE;
1076}
1077
1078BOOL LLComboBox::operateOnAll(EOperation op)
1079{
1080	if (op == OP_DELETE)
1081	{
1082		clearRows();
1083		return TRUE;
1084	}
1085	return FALSE;
1086}
1087
1088BOOL LLComboBox::selectItemRange( S32 first, S32 last )
1089{
1090	return mList->selectItemRange(first, last);
1091}
1092
1093
1094static LLDefaultChildRegistry::Register<LLIconsComboBox> register_icons_combo_box("icons_combo_box");
1095
1096LLIconsComboBox::Params::Params()
1097:	icon_column("icon_column", ICON_COLUMN),
1098	label_column("label_column", LABEL_COLUMN)
1099{}
1100
1101LLIconsComboBox::LLIconsComboBox(const LLIconsComboBox::Params& p)
1102:	LLComboBox(p),
1103	mIconColumnIndex(p.icon_column),
1104	mLabelColumnIndex(p.label_column)
1105{}
1106
1107const std::string LLIconsComboBox::getSelectedItemLabel(S32 column) const
1108{
1109	mButton->setImageOverlay(LLComboBox::getSelectedItemLabel(mIconColumnIndex), mButton->getImageOverlayHAlign());
1110
1111	return LLComboBox::getSelectedItemLabel(mLabelColumnIndex);
1112}