llscrolllistctrl.cpp
上传用户:king477883
上传日期:2021-03-01
资源大小:9553k
文件大小:69k
源码类别:

游戏引擎

开发平台:

C++ Builder

  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=viewergpl$
  7.  * 
  8.  * Copyright (c) 2001-2010, Linden Research, Inc.
  9.  * 
  10.  * Second Life Viewer Source Code
  11.  * The source code in this file ("Source Code") is provided by Linden Lab
  12.  * to you under the terms of the GNU General Public License, version 2.0
  13.  * ("GPL"), unless you have obtained a separate licensing agreement
  14.  * ("Other License"), formally executed by you and Linden Lab.  Terms of
  15.  * the GPL can be found in doc/GPL-license.txt in this distribution, or
  16.  * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
  17.  * 
  18.  * There are special exceptions to the terms and conditions of the GPL as
  19.  * it is applied to this Source Code. View the full text of the exception
  20.  * in the file doc/FLOSS-exception.txt in this software distribution, or
  21.  * online at
  22.  * http://secondlifegrid.net/programs/open_source/licensing/flossexception
  23.  * 
  24.  * By copying, modifying or distributing this software, you acknowledge
  25.  * that you have read and understood your obligations described above,
  26.  * and agree to abide by those obligations.
  27.  * 
  28.  * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
  29.  * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
  30.  * COMPLETENESS OR PERFORMANCE.
  31.  * $/LicenseInfo$
  32.  */
  33. #include "linden_common.h"
  34. #include "llscrolllistctrl.h"
  35. #include <algorithm>
  36. #include "llstl.h"
  37. #include "llboost.h"
  38. //#include "indra_constants.h"
  39. #include "llcheckboxctrl.h"
  40. #include "llclipboard.h"
  41. #include "llfocusmgr.h"
  42. #include "llgl.h" // LLGLSUIDefault()
  43. #include "lllocalcliprect.h"
  44. //#include "llrender.h"
  45. #include "llresmgr.h"
  46. #include "llscrollbar.h"
  47. #include "llscrolllistcell.h"
  48. #include "llstring.h"
  49. #include "llui.h"
  50. #include "lluictrlfactory.h"
  51. #include "llwindow.h"
  52. #include "llcontrol.h"
  53. #include "llkeyboard.h"
  54. #include "llviewborder.h"
  55. #include "lltextbox.h"
  56. #include "llsdparam.h"
  57. #include "llcachename.h"
  58. #include "llmenugl.h"
  59. #include "llurlaction.h"
  60. #include "lltooltip.h"
  61. #include <boost/bind.hpp>
  62. static LLDefaultChildRegistry::Register<LLScrollListCtrl> r("scroll_list");
  63. // local structures & classes.
  64. struct SortScrollListItem
  65. {
  66. SortScrollListItem(const std::vector<std::pair<S32, BOOL> >& sort_orders)
  67. : mSortOrders(sort_orders)
  68. {}
  69. bool operator()(const LLScrollListItem* i1, const LLScrollListItem* i2)
  70. {
  71. // sort over all columns in order specified by mSortOrders
  72. S32 sort_result = 0;
  73. for (sort_order_t::const_reverse_iterator it = mSortOrders.rbegin();
  74.  it != mSortOrders.rend(); ++it)
  75. {
  76. S32 col_idx = it->first;
  77. BOOL sort_ascending = it->second;
  78. const LLScrollListCell *cell1 = i1->getColumn(col_idx);
  79. const LLScrollListCell *cell2 = i2->getColumn(col_idx);
  80. S32 order = sort_ascending ? 1 : -1; // ascending or descending sort for this column?
  81. if (cell1 && cell2)
  82. {
  83. sort_result = order * LLStringUtil::compareDict(cell1->getValue().asString(), cell2->getValue().asString());
  84. if (sort_result != 0)
  85. {
  86. break; // we have a sort order!
  87. }
  88. }
  89. }
  90. return sort_result < 0;
  91. }
  92. typedef std::vector<std::pair<S32, BOOL> > sort_order_t;
  93. const sort_order_t& mSortOrders;
  94. };
  95. //---------------------------------------------------------------------------
  96. // LLScrollListCtrl
  97. //---------------------------------------------------------------------------
  98. LLScrollListCtrl::Contents::Contents()
  99. : columns("column"),
  100. rows("row")
  101. {
  102. addSynonym(columns, "columns");
  103. addSynonym(rows, "rows");
  104. }
  105. LLScrollListCtrl::Params::Params()
  106. : multi_select("multi_select", false),
  107. has_border("draw_border"),
  108. draw_heading("draw_heading"),
  109. search_column("search_column", 0),
  110. sort_column("sort_column", -1),
  111. sort_ascending("sort_ascending", true),
  112. commit_on_keyboard_movement("commit_on_keyboard_movement", true),
  113. heading_height("heading_height"),
  114. page_lines("page_lines", 0),
  115. background_visible("background_visible"),
  116. draw_stripes("draw_stripes"),
  117. column_padding("column_padding"),
  118. fg_unselected_color("fg_unselected_color"),
  119. fg_selected_color("fg_selected_color"),
  120. bg_selected_color("bg_selected_color"),
  121. fg_disable_color("fg_disable_color"),
  122. bg_writeable_color("bg_writeable_color"),
  123. bg_readonly_color("bg_readonly_color"),
  124. bg_stripe_color("bg_stripe_color"),
  125. hovered_color("hovered_color"),
  126. highlighted_color("highlighted_color"),
  127. contents(""),
  128. scroll_bar_bg_visible("scroll_bar_bg_visible"),
  129. scroll_bar_bg_color("scroll_bar_bg_color")
  130. , border("border")
  131. {
  132. name = "scroll_list";
  133. mouse_opaque = true;
  134. }
  135. LLScrollListCtrl::LLScrollListCtrl(const LLScrollListCtrl::Params& p)
  136. : LLUICtrl(p),
  137. mLineHeight(0),
  138. mScrollLines(0),
  139. mPageLines(p.page_lines),
  140. mMaxSelectable(0),
  141. mAllowKeyboardMovement(TRUE),
  142. mCommitOnKeyboardMovement(p.commit_on_keyboard_movement),
  143. mCommitOnSelectionChange(FALSE),
  144. mSelectionChanged(FALSE),
  145. mNeedsScroll(FALSE),
  146. mCanSelect(TRUE),
  147. mColumnsDirty(FALSE),
  148. mMaxItemCount(INT_MAX), 
  149. mMaxContentWidth(0),
  150. mBorderThickness( 2 ),
  151. mOnDoubleClickCallback( NULL ),
  152. mOnMaximumSelectCallback( NULL ),
  153. mOnSortChangedCallback( NULL ),
  154. mHighlightedItem(-1),
  155. mBorder(NULL),
  156. mPopupMenu(NULL),
  157. mNumDynamicWidthColumns(0),
  158. mTotalStaticColumnWidth(0),
  159. mTotalColumnPadding(0),
  160. mSorted(false),
  161. mDirty(FALSE),
  162. mOriginalSelection(-1),
  163. mLastSelected(NULL),
  164. mHeadingHeight(p.heading_height),
  165. mAllowMultipleSelection(p.multi_select),
  166. mDisplayColumnHeaders(p.draw_heading),
  167. mBackgroundVisible(p.background_visible),
  168. mDrawStripes(p.draw_stripes),
  169. mBgWriteableColor(p.bg_writeable_color()),
  170. mBgReadOnlyColor(p.bg_readonly_color()),
  171. mBgSelectedColor(p.bg_selected_color()),
  172. mBgStripeColor(p.bg_stripe_color()),
  173. mFgSelectedColor(p.fg_selected_color()),
  174. mFgUnselectedColor(p.fg_unselected_color()),
  175. mFgDisabledColor(p.fg_disable_color()),
  176. mHighlightedColor(p.highlighted_color()),
  177. mHoveredColor(p.hovered_color()),
  178. mSearchColumn(p.search_column),
  179. mColumnPadding(p.column_padding),
  180. mContextMenuType(MENU_NONE)
  181. {
  182. mItemListRect.setOriginAndSize(
  183. mBorderThickness,
  184. mBorderThickness,
  185. getRect().getWidth() - 2 * mBorderThickness,
  186. getRect().getHeight() - 2 * mBorderThickness );
  187. updateLineHeight();
  188. // Init the scrollbar
  189. static LLUICachedControl<S32> scrollbar_size ("UIScrollbarSize", 0);
  190. LLRect scroll_rect;
  191. scroll_rect.setOriginAndSize( 
  192. getRect().getWidth() - mBorderThickness - scrollbar_size,
  193. mItemListRect.mBottom,
  194. scrollbar_size,
  195. mItemListRect.getHeight());
  196. LLScrollbar::Params sbparams;
  197. sbparams.name("Scrollbar");
  198. sbparams.rect(scroll_rect);
  199. sbparams.orientation(LLScrollbar::VERTICAL);
  200. sbparams.doc_size(getItemCount());
  201. sbparams.doc_pos(mScrollLines);
  202. sbparams.page_size( getLinesPerPage() );
  203. sbparams.change_callback(boost::bind(&LLScrollListCtrl::onScrollChange, this, _1, _2));
  204. sbparams.follows.flags(FOLLOWS_RIGHT | FOLLOWS_TOP | FOLLOWS_BOTTOM);
  205. sbparams.visible(false);
  206. sbparams.bg_visible(p.scroll_bar_bg_visible);
  207. sbparams.bg_color(p.scroll_bar_bg_color);
  208. mScrollbar = LLUICtrlFactory::create<LLScrollbar> (sbparams);
  209. addChild(mScrollbar);
  210. // Border
  211. if (p.has_border)
  212. {
  213. LLRect border_rect = getLocalRect();
  214. LLViewBorder::Params params = p.border;
  215. params.rect(border_rect);
  216. mBorder = LLUICtrlFactory::create<LLViewBorder> (params);
  217. addChild(mBorder);
  218. }
  219. // set border *after* rect is fully initialized
  220. if (mBorder)
  221. {
  222. mBorder->setRect(getLocalRect());
  223. mBorder->reshape(getRect().getWidth(), getRect().getHeight());
  224. }
  225. if (p.sort_column >= 0)
  226. {
  227. sortByColumnIndex(p.sort_column, p.sort_ascending);
  228. }
  229. for (LLInitParam::ParamIterator<LLScrollListColumn::Params>::const_iterator row_it = p.contents.columns().begin();
  230. row_it != p.contents.columns().end();
  231. ++row_it)
  232. {
  233. addColumn(*row_it);
  234. }
  235. for (LLInitParam::ParamIterator<LLScrollListItem::Params>::const_iterator row_it = p.contents.rows().begin();
  236. row_it != p.contents.rows().end();
  237. ++row_it)
  238. {
  239. addRow(*row_it);
  240. }
  241. LLTextBox::Params text_p;
  242. text_p.name("comment_text");
  243. text_p.border_visible(false);
  244. text_p.rect(mItemListRect);
  245. text_p.follows.flags(FOLLOWS_ALL);
  246. addChild(LLUICtrlFactory::create<LLTextBox>(text_p));
  247. }
  248. S32 LLScrollListCtrl::getSearchColumn()
  249. {
  250. // search for proper search column
  251. if (mSearchColumn < 0)
  252. {
  253. LLScrollListItem* itemp = getFirstData();
  254. if (itemp)
  255. {
  256. for(S32 column = 0; column < getNumColumns(); column++)
  257. {
  258. LLScrollListCell* cell = itemp->getColumn(column);
  259. if (cell && cell->isText())
  260. {
  261. mSearchColumn = column;
  262. break;
  263. }
  264. }
  265. }
  266. }
  267. return llclamp(mSearchColumn, 0, getNumColumns());
  268. }
  269. /*virtual*/
  270. bool LLScrollListCtrl::preProcessChildNode(LLXMLNodePtr child)
  271. {
  272. if (child->hasName("column") || child->hasName("row"))
  273. {
  274. return true; // skip
  275. }
  276. else
  277. {
  278. return false;
  279. }
  280. }
  281. LLScrollListCtrl::~LLScrollListCtrl()
  282. {
  283. std::for_each(mItemList.begin(), mItemList.end(), DeletePointer());
  284. if( gEditMenuHandler == this )
  285. {
  286. gEditMenuHandler = NULL;
  287. }
  288. }
  289. BOOL LLScrollListCtrl::setMaxItemCount(S32 max_count)
  290. {
  291. if (max_count >= getItemCount())
  292. {
  293. mMaxItemCount = max_count;
  294. }
  295. return (max_count == mMaxItemCount);
  296. }
  297. S32 LLScrollListCtrl::isEmpty() const
  298. {
  299. return mItemList.empty();
  300. }
  301. S32 LLScrollListCtrl::getItemCount() const
  302. {
  303. return mItemList.size();
  304. }
  305. // virtual LLScrolListInterface function (was deleteAllItems)
  306. void LLScrollListCtrl::clearRows()
  307. {
  308. std::for_each(mItemList.begin(), mItemList.end(), DeletePointer());
  309. mItemList.clear();
  310. //mItemCount = 0;
  311. // Scroll the bar back up to the top.
  312. mScrollbar->setDocParams(0, 0);
  313. mScrollLines = 0;
  314. mLastSelected = NULL;
  315. updateLayout();
  316. mDirty = FALSE; 
  317. }
  318. LLScrollListItem* LLScrollListCtrl::getFirstSelected() const
  319. {
  320. item_list::const_iterator iter;
  321. for(iter = mItemList.begin(); iter != mItemList.end(); iter++)
  322. {
  323. LLScrollListItem* item  = *iter;
  324. if (item->getSelected())
  325. {
  326. return item;
  327. }
  328. }
  329. return NULL;
  330. }
  331. std::vector<LLScrollListItem*> LLScrollListCtrl::getAllSelected() const
  332. {
  333. std::vector<LLScrollListItem*> ret;
  334. item_list::const_iterator iter;
  335. for(iter = mItemList.begin(); iter != mItemList.end(); iter++)
  336. {
  337. LLScrollListItem* item  = *iter;
  338. if (item->getSelected())
  339. {
  340. ret.push_back(item);
  341. }
  342. }
  343. return ret;
  344. }
  345. S32 LLScrollListCtrl::getFirstSelectedIndex() const
  346. {
  347. S32 CurSelectedIndex = 0;
  348. // make sure sort is up to date before returning an index
  349. updateSort();
  350. item_list::const_iterator iter;
  351. for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
  352. {
  353. LLScrollListItem* item  = *iter;
  354. if (item->getSelected())
  355. {
  356. return CurSelectedIndex;
  357. }
  358. CurSelectedIndex++;
  359. }
  360. return -1;
  361. }
  362. LLScrollListItem* LLScrollListCtrl::getFirstData() const
  363. {
  364. if (mItemList.size() == 0)
  365. {
  366. return NULL;
  367. }
  368. return mItemList[0];
  369. }
  370. LLScrollListItem* LLScrollListCtrl::getLastData() const
  371. {
  372. if (mItemList.size() == 0)
  373. {
  374. return NULL;
  375. }
  376. return mItemList[mItemList.size() - 1];
  377. }
  378. std::vector<LLScrollListItem*> LLScrollListCtrl::getAllData() const
  379. {
  380. std::vector<LLScrollListItem*> ret;
  381. item_list::const_iterator iter;
  382. for(iter = mItemList.begin(); iter != mItemList.end(); iter++)
  383. {
  384. LLScrollListItem* item  = *iter;
  385. ret.push_back(item);
  386. }
  387. return ret;
  388. }
  389. // returns first matching item
  390. LLScrollListItem* LLScrollListCtrl::getItem(const LLSD& sd) const
  391. {
  392. std::string string_val = sd.asString();
  393. item_list::const_iterator iter;
  394. for(iter = mItemList.begin(); iter != mItemList.end(); iter++)
  395. {
  396. LLScrollListItem* item  = *iter;
  397. // assumes string representation is good enough for comparison
  398. if (item->getValue().asString() == string_val)
  399. {
  400. return item;
  401. }
  402. }
  403. return NULL;
  404. }
  405. void LLScrollListCtrl::reshape( S32 width, S32 height, BOOL called_from_parent )
  406. {
  407. LLUICtrl::reshape( width, height, called_from_parent );
  408. updateLayout();
  409. }
  410. void LLScrollListCtrl::updateLayout()
  411. {
  412. static LLUICachedControl<S32> scrollbar_size ("UIScrollbarSize", 0);
  413. // reserve room for column headers, if needed
  414. S32 heading_size = (mDisplayColumnHeaders ? mHeadingHeight : 0);
  415. mItemListRect.setOriginAndSize(
  416. mBorderThickness,
  417. mBorderThickness,
  418. getRect().getWidth() - 2 * mBorderThickness,
  419. getRect().getHeight() - (2 * mBorderThickness ) - heading_size );
  420. getChildView("comment_text")->setShape(mItemListRect);
  421. // how many lines of content in a single "page"
  422. S32 page_lines =  getLinesPerPage();
  423. BOOL scrollbar_visible = mLineHeight * getItemCount() > mItemListRect.getHeight();
  424. if (scrollbar_visible)
  425. {
  426. // provide space on the right for scrollbar
  427. mItemListRect.mRight = getRect().getWidth() - mBorderThickness - scrollbar_size;
  428. }
  429. mScrollbar->setOrigin(getRect().getWidth() - mBorderThickness - scrollbar_size, mItemListRect.mBottom);
  430. mScrollbar->reshape(scrollbar_size, mItemListRect.getHeight() + (mDisplayColumnHeaders ? mHeadingHeight : 0));
  431. mScrollbar->setPageSize(page_lines);
  432. mScrollbar->setDocSize( getItemCount() );
  433. mScrollbar->setVisible(scrollbar_visible);
  434. dirtyColumns();
  435. }
  436. // Attempt to size the control to show all items.
  437. // Do not make larger than width or height.
  438. void LLScrollListCtrl::fitContents(S32 max_width, S32 max_height)
  439. {
  440. S32 height = llmin( getRequiredRect().getHeight(), max_height );
  441. if(mPageLines)
  442. height = llmin( mPageLines * mLineHeight + 2*mBorderThickness + (mDisplayColumnHeaders ? mHeadingHeight : 0), height );
  443. S32 width = getRect().getWidth();
  444. reshape( width, height );
  445. }
  446. LLRect LLScrollListCtrl::getRequiredRect()
  447. {
  448. S32 heading_size = (mDisplayColumnHeaders ? mHeadingHeight : 0);
  449. S32 height = (mLineHeight * getItemCount()) 
  450. + (2 * mBorderThickness ) 
  451. + heading_size;
  452. S32 width = getRect().getWidth();
  453. return LLRect(0, height, width, 0);
  454. }
  455. BOOL LLScrollListCtrl::addItem( LLScrollListItem* item, EAddPosition pos, BOOL requires_column )
  456. {
  457. BOOL not_too_big = getItemCount() < mMaxItemCount;
  458. if (not_too_big)
  459. {
  460. switch( pos )
  461. {
  462. case ADD_TOP:
  463. mItemList.push_front(item);
  464. setNeedsSort();
  465. break;
  466. case ADD_SORTED:
  467. {
  468. // sort by column 0, in ascending order
  469. std::vector<sort_column_t> single_sort_column;
  470. single_sort_column.push_back(std::make_pair(0, TRUE));
  471. mItemList.push_back(item);
  472. std::stable_sort(
  473. mItemList.begin(), 
  474. mItemList.end(), 
  475. SortScrollListItem(single_sort_column));
  476. // ADD_SORTED just sorts by first column...
  477. // this might not match user sort criteria, so flag list as being in unsorted state
  478. setNeedsSort();
  479. break;
  480. }
  481. case ADD_BOTTOM:
  482. mItemList.push_back(item);
  483. setNeedsSort();
  484. break;
  485. default:
  486. llassert(0);
  487. mItemList.push_back(item);
  488. setNeedsSort();
  489. break;
  490. }
  491. // create new column on demand
  492. if (mColumns.empty() && requires_column)
  493. {
  494. LLScrollListColumn::Params col_params;
  495. col_params.name =  "default_column";
  496. col_params.header.label = "";
  497. col_params.width.dynamic_width = true;
  498. addColumn(col_params);
  499. }
  500. updateLineHeightInsert(item);
  501. updateLayout();
  502. }
  503. return not_too_big;
  504. }
  505. // NOTE: This is *very* expensive for large lists, especially when we are dirtying the list every frame
  506. //  while receiving a long list of names.
  507. // *TODO: Use bookkeeping to make this an incramental cost with item additions
  508. void LLScrollListCtrl::calcColumnWidths()
  509. {
  510. const S32 HEADING_TEXT_PADDING = 25;
  511. const S32 COLUMN_TEXT_PADDING = 10;
  512. mMaxContentWidth = 0;
  513. S32 max_item_width = 0;
  514. ordered_columns_t::iterator column_itor;
  515. for (column_itor = mColumnsIndexed.begin(); column_itor != mColumnsIndexed.end(); ++column_itor)
  516. {
  517. LLScrollListColumn* column = *column_itor;
  518. if (!column) continue;
  519. // update column width
  520. S32 new_width = column->getWidth();
  521. if (column->mRelWidth >= 0)
  522. {
  523. new_width = (S32)llround(column->mRelWidth*mItemListRect.getWidth());
  524. }
  525. else if (column->mDynamicWidth)
  526. {
  527. new_width = (mItemListRect.getWidth() - mTotalStaticColumnWidth - mTotalColumnPadding) / mNumDynamicWidthColumns;
  528. }
  529. column->setWidth(new_width);
  530. // update max content width for this column, by looking at all items
  531. column->mMaxContentWidth = column->mHeader ? LLFontGL::getFontSansSerifSmall()->getWidth(column->mLabel) + mColumnPadding + HEADING_TEXT_PADDING : 0;
  532. item_list::iterator iter;
  533. for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
  534. {
  535. LLScrollListCell* cellp = (*iter)->getColumn(column->mIndex);
  536. if (!cellp) continue;
  537. column->mMaxContentWidth = llmax(LLFontGL::getFontSansSerifSmall()->getWidth(cellp->getValue().asString()) + mColumnPadding + COLUMN_TEXT_PADDING, column->mMaxContentWidth);
  538. }
  539. max_item_width += column->mMaxContentWidth;
  540. }
  541. mMaxContentWidth = max_item_width;
  542. }
  543. const S32 SCROLL_LIST_ROW_PAD = 2;
  544. // Line height is the max height of all the cells in all the items.
  545. void LLScrollListCtrl::updateLineHeight()
  546. {
  547. mLineHeight = 0;
  548. item_list::iterator iter;
  549. for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
  550. {
  551. LLScrollListItem *itemp = *iter;
  552. S32 num_cols = itemp->getNumColumns();
  553. S32 i = 0;
  554. for (const LLScrollListCell* cell = itemp->getColumn(i); i < num_cols; cell = itemp->getColumn(++i))
  555. {
  556. mLineHeight = llmax( mLineHeight, cell->getHeight() + SCROLL_LIST_ROW_PAD );
  557. }
  558. }
  559. }
  560. // when the only change to line height is from an insert, we needn't scan the entire list
  561. void LLScrollListCtrl::updateLineHeightInsert(LLScrollListItem* itemp)
  562. {
  563. S32 num_cols = itemp->getNumColumns();
  564. S32 i = 0;
  565. for (const LLScrollListCell* cell = itemp->getColumn(i); i < num_cols; cell = itemp->getColumn(++i))
  566. {
  567. mLineHeight = llmax( mLineHeight, cell->getHeight() + SCROLL_LIST_ROW_PAD );
  568. }
  569. }
  570. void LLScrollListCtrl::updateColumns()
  571. {
  572. calcColumnWidths();
  573. // update column headers
  574. std::vector<LLScrollListColumn*>::iterator column_ordered_it;
  575. S32 left = mItemListRect.mLeft;
  576. LLScrollColumnHeader* last_header = NULL;
  577. for (column_ordered_it = mColumnsIndexed.begin(); column_ordered_it != mColumnsIndexed.end(); ++column_ordered_it)
  578. {
  579. if ((*column_ordered_it)->getWidth() < 0)
  580. {
  581. // skip hidden columns
  582. continue;
  583. }
  584. LLScrollListColumn* column = *column_ordered_it;
  585. if (column->mHeader)
  586. {
  587. column->mHeader->updateResizeBars();
  588. last_header = column->mHeader;
  589. S32 top = mItemListRect.mTop;
  590. S32 right = left + column->getWidth();
  591. if (column->mIndex != (S32)mColumnsIndexed.size()-1)
  592. {
  593. right += mColumnPadding;
  594. }
  595. right = llmax(left, llmin(mItemListRect.getWidth(), right));
  596. S32 header_width = right - left;
  597. last_header->reshape(header_width, mHeadingHeight);
  598. last_header->translate(
  599. left - last_header->getRect().mLeft,
  600. top - last_header->getRect().mBottom);
  601. last_header->setVisible(mDisplayColumnHeaders && header_width > 0);
  602. left = right;
  603. }
  604. }
  605. // expand last column header we encountered to full list width
  606. if (last_header)
  607. {
  608. S32 new_width = llmax(0, mItemListRect.mRight - last_header->getRect().mLeft);
  609. last_header->reshape(new_width, last_header->getRect().getHeight());
  610. last_header->setVisible(mDisplayColumnHeaders && new_width > 0);
  611. last_header->getColumn()->setWidth(new_width);
  612. }
  613. // propagate column widths to individual cells
  614. item_list::iterator iter;
  615. for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
  616. {
  617. LLScrollListItem *itemp = *iter;
  618. S32 num_cols = itemp->getNumColumns();
  619. S32 i = 0;
  620. for (LLScrollListCell* cell = itemp->getColumn(i); i < num_cols; cell = itemp->getColumn(++i))
  621. {
  622. if (i >= (S32)mColumnsIndexed.size()) break;
  623. cell->setWidth(mColumnsIndexed[i]->getWidth());
  624. }
  625. }
  626. }
  627. void LLScrollListCtrl::setHeadingHeight(S32 heading_height)
  628. {
  629. mHeadingHeight = heading_height;
  630. updateLayout();
  631. }
  632. void LLScrollListCtrl::setPageLines(S32 new_page_lines)
  633. {
  634. mPageLines  = new_page_lines;
  635. updateLayout();
  636. }
  637. BOOL LLScrollListCtrl::selectFirstItem()
  638. {
  639. BOOL success = FALSE;
  640. // our $%&@#$()^%#$()*^ iterators don't let us check against the first item inside out iteration
  641. BOOL first_item = TRUE;
  642. item_list::iterator iter;
  643. for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
  644. {
  645. LLScrollListItem *itemp = *iter;
  646. if( first_item && itemp->getEnabled() )
  647. {
  648. if (!itemp->getSelected())
  649. {
  650. selectItem(itemp);
  651. }
  652. success = TRUE;
  653. mOriginalSelection = 0;
  654. }
  655. else
  656. {
  657. deselectItem(itemp);
  658. }
  659. first_item = FALSE;
  660. }
  661. if (mCommitOnSelectionChange)
  662. {
  663. commitIfChanged();
  664. }
  665. return success;
  666. }
  667. // Deselects all other items
  668. // virtual
  669. BOOL LLScrollListCtrl::selectNthItem( S32 target_index )
  670. {
  671. return selectItemRange(target_index, target_index);
  672. }
  673. // virtual
  674. BOOL LLScrollListCtrl::selectItemRange( S32 first_index, S32 last_index )
  675. {
  676. if (mItemList.empty())
  677. {
  678. return FALSE;
  679. }
  680. // make sure sort is up to date
  681. updateSort();
  682. S32 listlen = (S32)mItemList.size();
  683. first_index = llclamp(first_index, 0, listlen-1);
  684. if (last_index < 0)
  685. last_index = listlen-1;
  686. else
  687. last_index = llclamp(last_index, first_index, listlen-1);
  688. BOOL success = FALSE;
  689. S32 index = 0;
  690. for (item_list::iterator iter = mItemList.begin(); iter != mItemList.end(); )
  691. {
  692. LLScrollListItem *itemp = *iter;
  693. if(!itemp)
  694. {
  695. iter = mItemList.erase(iter);
  696. continue ;
  697. }
  698. if( index >= first_index && index <= last_index )
  699. {
  700. if( itemp->getEnabled() )
  701. {
  702. selectItem(itemp, FALSE);
  703. success = TRUE;
  704. }
  705. }
  706. else
  707. {
  708. deselectItem(itemp);
  709. }
  710. index++;
  711. iter++ ;
  712. }
  713. if (mCommitOnSelectionChange)
  714. {
  715. commitIfChanged();
  716. }
  717. mSearchString.clear();
  718. return success;
  719. }
  720. void LLScrollListCtrl::swapWithNext(S32 index)
  721. {
  722. if (index >= ((S32)mItemList.size() - 1))
  723. {
  724. // At end of list, doesn't do anything
  725. return;
  726. }
  727. updateSort();
  728. LLScrollListItem *cur_itemp = mItemList[index];
  729. mItemList[index] = mItemList[index + 1];
  730. mItemList[index + 1] = cur_itemp;
  731. }
  732. void LLScrollListCtrl::swapWithPrevious(S32 index)
  733. {
  734. if (index <= 0)
  735. {
  736. // At beginning of list, don't do anything
  737. }
  738. updateSort();
  739. LLScrollListItem *cur_itemp = mItemList[index];
  740. mItemList[index] = mItemList[index - 1];
  741. mItemList[index - 1] = cur_itemp;
  742. }
  743. void LLScrollListCtrl::deleteSingleItem(S32 target_index)
  744. {
  745. if (target_index < 0 || target_index >= (S32)mItemList.size())
  746. {
  747. return;
  748. }
  749. updateSort();
  750. LLScrollListItem *itemp;
  751. itemp = mItemList[target_index];
  752. if (itemp == mLastSelected)
  753. {
  754. mLastSelected = NULL;
  755. }
  756. delete itemp;
  757. mItemList.erase(mItemList.begin() + target_index);
  758. dirtyColumns();
  759. }
  760. //FIXME: refactor item deletion
  761. void LLScrollListCtrl::deleteItems(const LLSD& sd)
  762. {
  763. item_list::iterator iter;
  764. for (iter = mItemList.begin(); iter < mItemList.end(); )
  765. {
  766. LLScrollListItem* itemp = *iter;
  767. if (itemp->getValue().asString() == sd.asString())
  768. {
  769. if (itemp == mLastSelected)
  770. {
  771. mLastSelected = NULL;
  772. }
  773. delete itemp;
  774. iter = mItemList.erase(iter);
  775. }
  776. else
  777. {
  778. iter++;
  779. }
  780. }
  781. dirtyColumns();
  782. }
  783. void LLScrollListCtrl::deleteSelectedItems()
  784. {
  785. item_list::iterator iter;
  786. for (iter = mItemList.begin(); iter < mItemList.end(); )
  787. {
  788. LLScrollListItem* itemp = *iter;
  789. if (itemp->getSelected())
  790. {
  791. delete itemp;
  792. iter = mItemList.erase(iter);
  793. }
  794. else
  795. {
  796. iter++;
  797. }
  798. }
  799. mLastSelected = NULL;
  800. dirtyColumns();
  801. }
  802. void LLScrollListCtrl::clearHighlightedItems()
  803. {
  804. for (item_list::iterator iter = mItemList.begin(); iter != mItemList.end(); ++iter)
  805. {
  806. (*iter)->setHighlighted(false);
  807. }
  808. }
  809. void LLScrollListCtrl::mouseOverHighlightNthItem(S32 target_index)
  810. {
  811. if (mHighlightedItem != target_index)
  812. {
  813. mHighlightedItem = target_index;
  814. }
  815. }
  816. S32 LLScrollListCtrl::selectMultiple( std::vector<LLUUID> ids )
  817. {
  818. item_list::iterator iter;
  819. S32 count = 0;
  820. for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
  821. {
  822. LLScrollListItem* item = *iter;
  823. std::vector<LLUUID>::iterator iditr;
  824. for(iditr = ids.begin(); iditr != ids.end(); ++iditr)
  825. {
  826. if (item->getEnabled() && (item->getUUID() == (*iditr)))
  827. {
  828. selectItem(item,FALSE);
  829. ++count;
  830. break;
  831. }
  832. }
  833. if(ids.end() != iditr) ids.erase(iditr);
  834. }
  835. if (mCommitOnSelectionChange)
  836. {
  837. commitIfChanged();
  838. }
  839. return count;
  840. }
  841. S32 LLScrollListCtrl::getItemIndex( LLScrollListItem* target_item ) const
  842. {
  843. updateSort();
  844. S32 index = 0;
  845. item_list::const_iterator iter;
  846. for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
  847. {
  848. LLScrollListItem *itemp = *iter;
  849. if (target_item == itemp)
  850. {
  851. return index;
  852. }
  853. index++;
  854. }
  855. return -1;
  856. }
  857. S32 LLScrollListCtrl::getItemIndex( const LLUUID& target_id ) const
  858. {
  859. updateSort();
  860. S32 index = 0;
  861. item_list::const_iterator iter;
  862. for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
  863. {
  864. LLScrollListItem *itemp = *iter;
  865. if (target_id == itemp->getUUID())
  866. {
  867. return index;
  868. }
  869. index++;
  870. }
  871. return -1;
  872. }
  873. void LLScrollListCtrl::selectPrevItem( BOOL extend_selection)
  874. {
  875. LLScrollListItem* prev_item = NULL;
  876. if (!getFirstSelected())
  877. {
  878. // select last item
  879. selectNthItem(getItemCount() - 1);
  880. }
  881. else
  882. {
  883. updateSort();
  884. item_list::iterator iter;
  885. for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
  886. {
  887. LLScrollListItem* cur_item = *iter;
  888. if (cur_item->getSelected())
  889. {
  890. if (prev_item)
  891. {
  892. selectItem(prev_item, !extend_selection);
  893. }
  894. else
  895. {
  896. reportInvalidInput();
  897. }
  898. break;
  899. }
  900. // don't allow navigation to disabled elements
  901. prev_item = cur_item->getEnabled() ? cur_item : prev_item;
  902. }
  903. }
  904. if ((mCommitOnSelectionChange || mCommitOnKeyboardMovement))
  905. {
  906. commitIfChanged();
  907. }
  908. mSearchString.clear();
  909. }
  910. void LLScrollListCtrl::selectNextItem( BOOL extend_selection)
  911. {
  912. LLScrollListItem* next_item = NULL;
  913. if (!getFirstSelected())
  914. {
  915. selectFirstItem();
  916. }
  917. else
  918. {
  919. updateSort();
  920. item_list::reverse_iterator iter;
  921. for (iter = mItemList.rbegin(); iter != mItemList.rend(); iter++)
  922. {
  923. LLScrollListItem* cur_item = *iter;
  924. if (cur_item->getSelected())
  925. {
  926. if (next_item)
  927. {
  928. selectItem(next_item, !extend_selection);
  929. }
  930. else
  931. {
  932. reportInvalidInput();
  933. }
  934. break;
  935. }
  936. // don't allow navigation to disabled items
  937. next_item = cur_item->getEnabled() ? cur_item : next_item;
  938. }
  939. }
  940. if (mCommitOnKeyboardMovement)
  941. {
  942. onCommit();
  943. }
  944. mSearchString.clear();
  945. }
  946. void LLScrollListCtrl::deselectAllItems(BOOL no_commit_on_change)
  947. {
  948. item_list::iterator iter;
  949. for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
  950. {
  951. LLScrollListItem* item = *iter;
  952. deselectItem(item);
  953. }
  954. if (mCommitOnSelectionChange && !no_commit_on_change)
  955. {
  956. commitIfChanged();
  957. }
  958. }
  959. ///////////////////////////////////////////////////////////////////////////////////////////////////
  960. // Use this to add comment text such as "Searching", which ignores column settings of list
  961. void LLScrollListCtrl::setCommentText(const std::string& comment_text)
  962. {
  963. getChild<LLTextBox>("comment_text")->setValue(comment_text);
  964. }
  965. LLScrollListItem* LLScrollListCtrl::addSeparator(EAddPosition pos)
  966. {
  967. LLScrollListItem::Params separator_params;
  968. separator_params.enabled(false);
  969. LLScrollListCell::Params column_params;
  970. column_params.type = "icon";
  971. column_params.value = "menu_separator";
  972. column_params.color = LLColor4(0.f, 0.f, 0.f, 0.7f);
  973. column_params.font_halign = LLFontGL::HCENTER;
  974. separator_params.columns.add(column_params);
  975. return addRow( separator_params, pos );
  976. }
  977. // Selects first enabled item of the given name.
  978. // Returns false if item not found.
  979. // Calls getItemByLabel in order to combine functionality
  980. BOOL LLScrollListCtrl::selectItemByLabel(const std::string& label, BOOL case_sensitive)
  981. {
  982. deselectAllItems(TRUE);  // ensure that no stale items are selected, even if we don't find a match
  983. LLScrollListItem* item = getItemByLabel(label, case_sensitive);
  984. bool found = NULL != item;
  985. if(found)
  986. {
  987. selectItem(item);
  988. }
  989. if (mCommitOnSelectionChange)
  990. {
  991. commitIfChanged();
  992. }
  993. return found;
  994. }
  995. LLScrollListItem* LLScrollListCtrl::getItemByLabel(const std::string& label, BOOL case_sensitive, S32 column)
  996. {
  997. if (label.empty())  //RN: assume no empty items
  998. {
  999. return NULL;
  1000. }
  1001. std::string target_text = label;
  1002. if (!case_sensitive)
  1003. {
  1004. LLStringUtil::toLower(target_text);
  1005. }
  1006. item_list::iterator iter;
  1007. for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
  1008. {
  1009. LLScrollListItem* item = *iter;
  1010. std::string item_text = item->getColumn(column)->getValue().asString(); // Only select enabled items with matching names
  1011. if (!case_sensitive)
  1012. {
  1013. LLStringUtil::toLower(item_text);
  1014. }
  1015. if(item_text == target_text)
  1016. {
  1017. return item;
  1018. }
  1019. }
  1020. return NULL;
  1021. }
  1022. BOOL LLScrollListCtrl::selectItemByPrefix(const std::string& target, BOOL case_sensitive)
  1023. {
  1024. return selectItemByPrefix(utf8str_to_wstring(target), case_sensitive);
  1025. }
  1026. // Selects first enabled item that has a name where the name's first part matched the target string.
  1027. // Returns false if item not found.
  1028. BOOL LLScrollListCtrl::selectItemByPrefix(const LLWString& target, BOOL case_sensitive)
  1029. {
  1030. BOOL found = FALSE;
  1031. LLWString target_trimmed( target );
  1032. S32 target_len = target_trimmed.size();
  1033. if( 0 == target_len )
  1034. {
  1035. // Is "" a valid choice?
  1036. item_list::iterator iter;
  1037. for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
  1038. {
  1039. LLScrollListItem* item = *iter;
  1040. // Only select enabled items with matching names
  1041. LLScrollListCell* cellp = item->getColumn(getSearchColumn());
  1042. BOOL select = cellp ? item->getEnabled() && ('' == cellp->getValue().asString()[0]) : FALSE;
  1043. if (select)
  1044. {
  1045. selectItem(item);
  1046. found = TRUE;
  1047. break;
  1048. }
  1049. }
  1050. }
  1051. else
  1052. {
  1053. if (!case_sensitive)
  1054. {
  1055. // do comparisons in lower case
  1056. LLWStringUtil::toLower(target_trimmed);
  1057. }
  1058. for (item_list::iterator iter = mItemList.begin(); iter != mItemList.end(); iter++)
  1059. {
  1060. LLScrollListItem* item = *iter;
  1061. // Only select enabled items with matching names
  1062. LLScrollListCell* cellp = item->getColumn(getSearchColumn());
  1063. if (!cellp)
  1064. {
  1065. continue;
  1066. }
  1067. LLWString item_label = utf8str_to_wstring(cellp->getValue().asString());
  1068. if (!case_sensitive)
  1069. {
  1070. LLWStringUtil::toLower(item_label);
  1071. }
  1072. // remove extraneous whitespace from searchable label
  1073. LLWString trimmed_label = item_label;
  1074. LLWStringUtil::trim(trimmed_label);
  1075. BOOL select = item->getEnabled() && trimmed_label.compare(0, target_trimmed.size(), target_trimmed) == 0;
  1076. if (select)
  1077. {
  1078. // find offset of matching text (might have leading whitespace)
  1079. S32 offset = item_label.find(target_trimmed);
  1080. cellp->highlightText(offset, target_trimmed.size());
  1081. selectItem(item);
  1082. found = TRUE;
  1083. break;
  1084. }
  1085. }
  1086. }
  1087. if (mCommitOnSelectionChange)
  1088. {
  1089. commitIfChanged();
  1090. }
  1091. return found;
  1092. }
  1093. const std::string LLScrollListCtrl::getSelectedItemLabel(S32 column) const
  1094. {
  1095. LLScrollListItem* item;
  1096. item = getFirstSelected();
  1097. if (item)
  1098. {
  1099. return item->getColumn(column)->getValue().asString();
  1100. }
  1101. return LLStringUtil::null;
  1102. }
  1103. ///////////////////////////////////////////////////////////////////////////////////////////////////
  1104. // "StringUUID" interface: use this when you're creating a list that contains non-unique strings each of which
  1105. // has an associated, unique UUID, and only one of which can be selected at a time.
  1106. LLScrollListItem* LLScrollListCtrl::addStringUUIDItem(const std::string& item_text, const LLUUID& id, EAddPosition pos, BOOL enabled)
  1107. {
  1108. if (getItemCount() < mMaxItemCount)
  1109. {
  1110. LLScrollListItem::Params item_p;
  1111. item_p.enabled(enabled);
  1112. item_p.value(id);
  1113. item_p.columns.add().value(item_text).type("text");
  1114. return addRow( item_p, pos );
  1115. }
  1116. return NULL;
  1117. }
  1118. // Select the line or lines that match this UUID
  1119. BOOL LLScrollListCtrl::selectByID( const LLUUID& id )
  1120. {
  1121. return selectByValue( LLSD(id) );
  1122. }
  1123. BOOL LLScrollListCtrl::setSelectedByValue(const LLSD& value, BOOL selected)
  1124. {
  1125. BOOL found = FALSE;
  1126. if (selected && !mAllowMultipleSelection) deselectAllItems(TRUE);
  1127. item_list::iterator iter;
  1128. for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
  1129. {
  1130. LLScrollListItem* item = *iter;
  1131. if (item->getEnabled() && (item->getValue().asString() == value.asString()))
  1132. {
  1133. if (selected)
  1134. {
  1135. selectItem(item);
  1136. }
  1137. else
  1138. {
  1139. deselectItem(item);
  1140. }
  1141. found = TRUE;
  1142. break;
  1143. }
  1144. }
  1145. if (mCommitOnSelectionChange)
  1146. {
  1147. commitIfChanged();
  1148. }
  1149. return found;
  1150. }
  1151. BOOL LLScrollListCtrl::isSelected(const LLSD& value) const 
  1152. {
  1153. item_list::const_iterator iter;
  1154. for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
  1155. {
  1156. LLScrollListItem* item = *iter;
  1157. if (item->getValue().asString() == value.asString())
  1158. {
  1159. return item->getSelected();
  1160. }
  1161. }
  1162. return FALSE;
  1163. }
  1164. LLUUID LLScrollListCtrl::getStringUUIDSelectedItem() const
  1165. {
  1166. LLScrollListItem* item = getFirstSelected();
  1167. if (item)
  1168. {
  1169. return item->getUUID();
  1170. }
  1171. return LLUUID::null;
  1172. }
  1173. LLSD LLScrollListCtrl::getSelectedValue()
  1174. {
  1175. LLScrollListItem* item = getFirstSelected();
  1176. if (item)
  1177. {
  1178. return item->getValue();
  1179. }
  1180. else
  1181. {
  1182. return LLSD();
  1183. }
  1184. }
  1185. void LLScrollListCtrl::drawItems()
  1186. {
  1187. S32 x = mItemListRect.mLeft;
  1188. S32 y = mItemListRect.mTop - mLineHeight;
  1189. // allow for partial line at bottom
  1190. S32 num_page_lines = getLinesPerPage();
  1191. LLRect item_rect;
  1192. LLGLSUIDefault gls_ui;
  1193. F32 alpha = getDrawContext().mAlpha;
  1194. {
  1195. LLLocalClipRect clip(mItemListRect);
  1196. S32 cur_y = y;
  1197. S32 line = 0;
  1198. S32 max_columns = 0;
  1199. LLColor4 highlight_color = LLColor4::white;
  1200. static LLUICachedControl<F32> type_ahead_timeout ("TypeAheadTimeout", 0);
  1201. highlight_color.mV[VALPHA] = clamp_rescale(mSearchTimer.getElapsedTimeF32(), type_ahead_timeout * 0.7f, type_ahead_timeout, 0.4f, 0.f);
  1202. item_list::iterator iter;
  1203. for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
  1204. {
  1205. LLScrollListItem* item = *iter;
  1206. item_rect.setOriginAndSize( 
  1207. x, 
  1208. cur_y, 
  1209. mItemListRect.getWidth(),
  1210. mLineHeight );
  1211. item->setRect(item_rect);
  1212. //llinfos << item_rect.getWidth() << llendl;
  1213. max_columns = llmax(max_columns, item->getNumColumns());
  1214. LLColor4 fg_color;
  1215. LLColor4 bg_color(LLColor4::transparent);
  1216. if( mScrollLines <= line && line < mScrollLines + num_page_lines )
  1217. {
  1218. fg_color = (item->getEnabled() ? mFgUnselectedColor.get() : mFgDisabledColor.get());
  1219. if( item->getSelected() && mCanSelect)
  1220. {
  1221. if(item->getHighlighted()) // if it's highlighted, average the colors
  1222. {
  1223. bg_color = lerp(mBgSelectedColor.get(), mHighlightedColor.get(), 0.5f);
  1224. }
  1225. else // otherwise just select-highlight it
  1226. {
  1227. bg_color = mBgSelectedColor.get();
  1228. }
  1229. fg_color = (item->getEnabled() ? mFgSelectedColor.get() : mFgDisabledColor.get());
  1230. }
  1231. else if (mHighlightedItem == line && mCanSelect)
  1232. {
  1233. if(item->getHighlighted()) // if it's highlighted, average the colors
  1234. {
  1235. bg_color = lerp(mHoveredColor.get(), mHighlightedColor.get(), 0.5f);
  1236. }
  1237. else // otherwise just hover-highlight it
  1238. {
  1239. bg_color = mHoveredColor.get();
  1240. }
  1241. }
  1242. else if (item->getHighlighted())
  1243. {
  1244. bg_color = mHighlightedColor.get();
  1245. }
  1246. else 
  1247. {
  1248. if (mDrawStripes && (line % 2 == 0) && (max_columns > 1))
  1249. {
  1250. bg_color = mBgStripeColor.get();
  1251. }
  1252. }
  1253. if (!item->getEnabled())
  1254. {
  1255. bg_color = mBgReadOnlyColor.get();
  1256. }
  1257. item->draw(item_rect, fg_color % alpha, bg_color% alpha, highlight_color % alpha, mColumnPadding);
  1258. cur_y -= mLineHeight;
  1259. }
  1260. line++;
  1261. }
  1262. }
  1263. }
  1264. void LLScrollListCtrl::draw()
  1265. {
  1266. LLLocalClipRect clip(getLocalRect());
  1267. // if user specifies sort, make sure it is maintained
  1268. updateSort();
  1269. if (mNeedsScroll)
  1270. {
  1271. scrollToShowSelected();
  1272. mNeedsScroll = FALSE;
  1273. }
  1274. LLRect background(0, getRect().getHeight(), getRect().getWidth(), 0);
  1275. // Draw background
  1276. if (mBackgroundVisible)
  1277. {
  1278. gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
  1279. gl_rect_2d(background, getEnabled() ? mBgWriteableColor.get() : mBgReadOnlyColor.get() );
  1280. }
  1281. if (mColumnsDirty)
  1282. {
  1283. updateColumns();
  1284. mColumnsDirty = FALSE;
  1285. }
  1286. getChildView("comment_text")->setVisible(mItemList.empty());
  1287. drawItems();
  1288. if (mBorder)
  1289. {
  1290. mBorder->setKeyboardFocusHighlight(hasFocus());
  1291. }
  1292. LLUICtrl::draw();
  1293. }
  1294. void LLScrollListCtrl::setEnabled(BOOL enabled)
  1295. {
  1296. mCanSelect = enabled;
  1297. setTabStop(enabled);
  1298. mScrollbar->setTabStop(!enabled && mScrollbar->getPageSize() < mScrollbar->getDocSize());
  1299. }
  1300. BOOL LLScrollListCtrl::handleScrollWheel(S32 x, S32 y, S32 clicks)
  1301. {
  1302. BOOL handled = FALSE;
  1303. // Pretend the mouse is over the scrollbar
  1304. handled = mScrollbar->handleScrollWheel( 0, 0, clicks );
  1305. return handled;
  1306. }
  1307. // *NOTE: Requires a valid row_index and column_index
  1308. LLRect LLScrollListCtrl::getCellRect(S32 row_index, S32 column_index)
  1309. {
  1310. LLRect cell_rect;
  1311. S32 rect_left = getColumnOffsetFromIndex(column_index) + mItemListRect.mLeft;
  1312. S32 rect_bottom = getRowOffsetFromIndex(row_index);
  1313. LLScrollListColumn* columnp = getColumn(column_index);
  1314. cell_rect.setOriginAndSize(rect_left, rect_bottom,
  1315. /*rect_left + */columnp->getWidth(), mLineHeight);
  1316. return cell_rect;
  1317. }
  1318. BOOL LLScrollListCtrl::handleToolTip(S32 x, S32 y, MASK mask)
  1319. {
  1320. S32 column_index = getColumnIndexFromOffset(x);
  1321. LLScrollListColumn* columnp = getColumn(column_index);
  1322. if (columnp == NULL) return FALSE;
  1323. BOOL handled = FALSE;
  1324. // show tooltip for full name of hovered item if it has been truncated
  1325. LLScrollListItem* hit_item = hitItem(x, y);
  1326. if (hit_item)
  1327. {
  1328. LLScrollListCell* hit_cell = hit_item->getColumn(column_index);
  1329. if (!hit_cell) return FALSE;
  1330. if (hit_cell 
  1331. && hit_cell->isText()
  1332. && hit_cell->needsToolTip())
  1333. {
  1334. S32 row_index = getItemIndex(hit_item);
  1335. LLRect cell_rect = getCellRect(row_index, column_index);
  1336. // Convert rect local to screen coordinates
  1337. LLRect sticky_rect;
  1338. localRectToScreen(cell_rect, &sticky_rect);
  1339. // display tooltip exactly over original cell, in same font
  1340. LLToolTipMgr::instance().show(LLToolTip::Params()
  1341. .message(hit_cell->getToolTip())
  1342. .font(LLFontGL::getFontSansSerifSmall())
  1343. .pos(LLCoordGL(sticky_rect.mLeft - 5, sticky_rect.mTop + 6))
  1344. .delay_time(0.2f)
  1345. .sticky_rect(sticky_rect));
  1346. }
  1347. handled = TRUE;
  1348. }
  1349. // otherwise, look for a tooltip associated with this column
  1350. LLScrollColumnHeader* headerp = columnp->mHeader;
  1351. if (headerp && !handled)
  1352. {
  1353. handled = headerp->handleToolTip(x, y, mask);
  1354. }
  1355. return handled;
  1356. }
  1357. BOOL LLScrollListCtrl::selectItemAt(S32 x, S32 y, MASK mask)
  1358. {
  1359. if (!mCanSelect) return FALSE;
  1360. BOOL selection_changed = FALSE;
  1361. LLScrollListItem* hit_item = hitItem(x, y);
  1362. if( hit_item )
  1363. {
  1364. if( mAllowMultipleSelection )
  1365. {
  1366. if (mask & MASK_SHIFT)
  1367. {
  1368. if (mLastSelected == NULL)
  1369. {
  1370. selectItem(hit_item);
  1371. }
  1372. else
  1373. {
  1374. // Select everthing between mLastSelected and hit_item
  1375. bool selecting = false;
  1376. item_list::iterator itor;
  1377. // If we multiselect backwards, we'll stomp on mLastSelected,
  1378. // meaning that we never stop selecting until hitting max or
  1379. // the end of the list.
  1380. LLScrollListItem* lastSelected = mLastSelected;
  1381. for (itor = mItemList.begin(); itor != mItemList.end(); ++itor)
  1382. {
  1383. if(mMaxSelectable > 0 && getAllSelected().size() >= mMaxSelectable)
  1384. {
  1385. if(mOnMaximumSelectCallback)
  1386. {
  1387. mOnMaximumSelectCallback();
  1388. }
  1389. break;
  1390. }
  1391. LLScrollListItem *item = *itor;
  1392.                         if (item == hit_item || item == lastSelected)
  1393. {
  1394. selectItem(item, FALSE);
  1395. selecting = !selecting;
  1396. if (hit_item == lastSelected)
  1397. {
  1398. // stop selecting now, since we just clicked on our last selected item
  1399. selecting = FALSE;
  1400. }
  1401. }
  1402. if (selecting)
  1403. {
  1404. selectItem(item, FALSE);
  1405. }
  1406. }
  1407. }
  1408. }
  1409. else if (mask & MASK_CONTROL)
  1410. {
  1411. if (hit_item->getSelected())
  1412. {
  1413. deselectItem(hit_item);
  1414. }
  1415. else
  1416. {
  1417. if(!(mMaxSelectable > 0 && getAllSelected().size() >= mMaxSelectable))
  1418. {
  1419. selectItem(hit_item, FALSE);
  1420. }
  1421. else
  1422. {
  1423. if(mOnMaximumSelectCallback)
  1424. {
  1425. mOnMaximumSelectCallback();
  1426. }
  1427. }
  1428. }
  1429. }
  1430. else
  1431. {
  1432. deselectAllItems(TRUE);
  1433. selectItem(hit_item);
  1434. }
  1435. }
  1436. else
  1437. {
  1438. selectItem(hit_item);
  1439. }
  1440. selection_changed = mSelectionChanged;
  1441. if (mCommitOnSelectionChange)
  1442. {
  1443. commitIfChanged();
  1444. }
  1445. // clear search string on mouse operations
  1446. mSearchString.clear();
  1447. }
  1448. else
  1449. {
  1450. //mLastSelected = NULL;
  1451. //deselectAllItems(TRUE);
  1452. }
  1453. return selection_changed;
  1454. }
  1455. BOOL LLScrollListCtrl::handleMouseDown(S32 x, S32 y, MASK mask)
  1456. {
  1457. BOOL handled = childrenHandleMouseDown(x, y, mask) != NULL;
  1458. if( !handled )
  1459. {
  1460. // set keyboard focus first, in case click action wants to move focus elsewhere
  1461. setFocus(TRUE);
  1462. // clear selection changed flag because user is starting a selection operation
  1463. mSelectionChanged = FALSE;
  1464. handleClick(x, y, mask);
  1465. }
  1466. return TRUE;
  1467. }
  1468. BOOL LLScrollListCtrl::handleMouseUp(S32 x, S32 y, MASK mask)
  1469. {
  1470. if (hasMouseCapture())
  1471. {
  1472. // release mouse capture immediately so 
  1473. // scroll to show selected logic will work
  1474. gFocusMgr.setMouseCapture(NULL);
  1475. if(mask == MASK_NONE)
  1476. {
  1477. selectItemAt(x, y, mask);
  1478. mNeedsScroll = TRUE;
  1479. }
  1480. }
  1481. // always commit when mouse operation is completed inside list
  1482. if (mItemListRect.pointInRect(x,y))
  1483. {
  1484. mDirty |= mSelectionChanged;
  1485. mSelectionChanged = FALSE;
  1486. onCommit();
  1487. }
  1488. return LLUICtrl::handleMouseUp(x, y, mask);
  1489. }
  1490. // virtual
  1491. BOOL LLScrollListCtrl::handleRightMouseDown(S32 x, S32 y, MASK mask)
  1492. {
  1493. LLScrollListItem *item = hitItem(x, y);
  1494. if (item)
  1495. {
  1496. // check to see if we have a UUID for this row
  1497. std::string id = item->getValue().asString();
  1498. LLUUID uuid(id);
  1499. if (! uuid.isNull() && mContextMenuType != MENU_NONE)
  1500. {
  1501. // set up the callbacks for all of the avatar/group menu items
  1502. // (N.B. callbacks don't take const refs as id is local scope)
  1503. bool is_group = (mContextMenuType == MENU_GROUP);
  1504. LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar;
  1505. registrar.add("Url.Execute", boost::bind(&LLScrollListCtrl::showNameDetails, id, is_group));
  1506. registrar.add("Url.CopyLabel", boost::bind(&LLScrollListCtrl::copyNameToClipboard, id, is_group));
  1507. registrar.add("Url.CopyUrl", boost::bind(&LLScrollListCtrl::copySLURLToClipboard, id, is_group));
  1508. // create the context menu from the XUI file and display it
  1509. std::string menu_name = is_group ? "menu_url_group.xml" : "menu_url_agent.xml";
  1510. delete mPopupMenu;
  1511. mPopupMenu = LLUICtrlFactory::getInstance()->createFromFile<LLContextMenu>(
  1512. menu_name, LLMenuGL::sMenuContainer, LLMenuHolderGL::child_registry_t::instance());
  1513. if (mPopupMenu)
  1514. {
  1515. mPopupMenu->show(x, y);
  1516. LLMenuGL::showPopup(this, mPopupMenu, x, y);
  1517. return TRUE;
  1518. }
  1519. }
  1520. }
  1521. return FALSE;
  1522. }
  1523. void LLScrollListCtrl::showNameDetails(std::string id, bool is_group)
  1524. {
  1525. // show the resident's profile or the group profile
  1526. std::string sltype = is_group ? "group" : "agent";
  1527. std::string slurl = "secondlife:///app/" + sltype + "/" + id + "/about";
  1528. LLUrlAction::clickAction(slurl);
  1529. }
  1530. void LLScrollListCtrl::copyNameToClipboard(std::string id, bool is_group)
  1531. {
  1532. // copy the name of the avatar or group to the clipboard
  1533. std::string name;
  1534. if (is_group)
  1535. {
  1536. gCacheName->getGroupName(LLUUID(id), name);
  1537. }
  1538. else
  1539. {
  1540. gCacheName->getFullName(LLUUID(id), name);
  1541. }
  1542. LLUrlAction::copyURLToClipboard(name);
  1543. }
  1544. void LLScrollListCtrl::copySLURLToClipboard(std::string id, bool is_group)
  1545. {
  1546. // copy a SLURL for the avatar or group to the clipboard
  1547. std::string sltype = is_group ? "group" : "agent";
  1548. std::string slurl = "secondlife:///app/" + sltype + "/" + id + "/about";
  1549. LLUrlAction::copyURLToClipboard(slurl);
  1550. }
  1551. BOOL LLScrollListCtrl::handleDoubleClick(S32 x, S32 y, MASK mask)
  1552. {
  1553. //BOOL handled = FALSE;
  1554. BOOL handled = handleClick(x, y, mask);
  1555. if (!handled)
  1556. {
  1557. // Offer the click to the children, even if we aren't enabled
  1558. // so the scroll bars will work.
  1559. if (NULL == LLView::childrenHandleDoubleClick(x, y, mask))
  1560. {
  1561. // Run the callback only if an item is being double-clicked.
  1562. if( mCanSelect && hitItem(x, y) && mOnDoubleClickCallback )
  1563. {
  1564. mOnDoubleClickCallback();
  1565. }
  1566. }
  1567. }
  1568. return TRUE;
  1569. }
  1570. BOOL LLScrollListCtrl::handleClick(S32 x, S32 y, MASK mask)
  1571. {
  1572. // which row was clicked on?
  1573. LLScrollListItem* hit_item = hitItem(x, y);
  1574. if (!hit_item) return FALSE;
  1575. // get appropriate cell from that row
  1576. S32 column_index = getColumnIndexFromOffset(x);
  1577. LLScrollListCell* hit_cell = hit_item->getColumn(column_index);
  1578. if (!hit_cell) return FALSE;
  1579. // if cell handled click directly (i.e. clicked on an embedded checkbox)
  1580. if (hit_cell->handleClick())
  1581. {
  1582. // if item not currently selected, select it
  1583. if (!hit_item->getSelected())
  1584. {
  1585. selectItemAt(x, y, mask);
  1586. gFocusMgr.setMouseCapture(this);
  1587. mNeedsScroll = TRUE;
  1588. }
  1589. // propagate state of cell to rest of selected column
  1590. {
  1591. // propagate value of this cell to other selected items
  1592. // and commit the respective widgets
  1593. LLSD item_value = hit_cell->getValue();
  1594. for (item_list::iterator iter = mItemList.begin(); iter != mItemList.end(); iter++)
  1595. {
  1596. LLScrollListItem* item = *iter;
  1597. if (item->getSelected())
  1598. {
  1599. LLScrollListCell* cellp = item->getColumn(column_index);
  1600. cellp->setValue(item_value);
  1601. cellp->onCommit();
  1602. }
  1603. }
  1604. //FIXME: find a better way to signal cell changes
  1605. onCommit();
  1606. }
  1607. // eat click (e.g. do not trigger double click callback)
  1608. return TRUE;
  1609. }
  1610. else
  1611. {
  1612. // treat this as a normal single item selection
  1613. selectItemAt(x, y, mask);
  1614. gFocusMgr.setMouseCapture(this);
  1615. mNeedsScroll = TRUE;
  1616. // do not eat click (allow double click callback)
  1617. return FALSE;
  1618. }
  1619. }
  1620. LLScrollListItem* LLScrollListCtrl::hitItem( S32 x, S32 y )
  1621. {
  1622. // Excludes disabled items.
  1623. LLScrollListItem* hit_item = NULL;
  1624. updateSort();
  1625. LLRect item_rect;
  1626. item_rect.setLeftTopAndSize( 
  1627. mItemListRect.mLeft,
  1628. mItemListRect.mTop,
  1629. mItemListRect.getWidth(),
  1630. mLineHeight );
  1631. // allow for partial line at bottom
  1632. S32 num_page_lines = getLinesPerPage();
  1633. S32 line = 0;
  1634. item_list::iterator iter;
  1635. for(iter = mItemList.begin(); iter != mItemList.end(); iter++)
  1636. {
  1637. LLScrollListItem* item  = *iter;
  1638. if( mScrollLines <= line && line < mScrollLines + num_page_lines )
  1639. {
  1640. if( item->getEnabled() && item_rect.pointInRect( x, y ) )
  1641. {
  1642. hit_item = item;
  1643. break;
  1644. }
  1645. item_rect.translate(0, -mLineHeight);
  1646. }
  1647. line++;
  1648. }
  1649. return hit_item;
  1650. }
  1651. S32 LLScrollListCtrl::getColumnIndexFromOffset(S32 x)
  1652. {
  1653. // which column did we hit?
  1654. S32 left = 0;
  1655. S32 right = 0;
  1656. S32 width = 0;
  1657. S32 column_index = 0;
  1658. ordered_columns_t::const_iterator iter = mColumnsIndexed.begin();
  1659. ordered_columns_t::const_iterator end = mColumnsIndexed.end();
  1660. for ( ; iter != end; ++iter)
  1661. {
  1662. width = (*iter)->getWidth() + mColumnPadding;
  1663. right += width;
  1664. if (left <= x && x < right )
  1665. {
  1666. break;
  1667. }
  1668. // set left for next column as right of current column
  1669. left = right;
  1670. column_index++;
  1671. }
  1672. return llclamp(column_index, 0, getNumColumns() - 1);
  1673. }
  1674. S32 LLScrollListCtrl::getColumnOffsetFromIndex(S32 index)
  1675. {
  1676. S32 column_offset = 0;
  1677. ordered_columns_t::const_iterator iter = mColumnsIndexed.begin();
  1678. ordered_columns_t::const_iterator end = mColumnsIndexed.end();
  1679. for ( ; iter != end; ++iter)
  1680. {
  1681. if (index-- <= 0)
  1682. {
  1683. return column_offset;
  1684. }
  1685. column_offset += (*iter)->getWidth() + mColumnPadding;
  1686. }
  1687. // when running off the end, return the rightmost pixel
  1688. return mItemListRect.mRight;
  1689. }
  1690. S32 LLScrollListCtrl::getRowOffsetFromIndex(S32 index)
  1691. {
  1692. S32 row_bottom = (mItemListRect.mTop - ((index - mScrollLines + 1) * mLineHeight) );
  1693. return row_bottom;
  1694. }
  1695. BOOL LLScrollListCtrl::handleHover(S32 x,S32 y,MASK mask)
  1696. {
  1697. BOOL handled = FALSE;
  1698. if (hasMouseCapture())
  1699. {
  1700. if(mask == MASK_NONE)
  1701. {
  1702. selectItemAt(x, y, mask);
  1703. mNeedsScroll = TRUE;
  1704. }
  1705. }
  1706. else 
  1707. if (mCanSelect)
  1708. {
  1709. LLScrollListItem* item = hitItem(x, y);
  1710. if (item)
  1711. {
  1712. mouseOverHighlightNthItem(getItemIndex(item));
  1713. }
  1714. else
  1715. {
  1716. mouseOverHighlightNthItem(-1);
  1717. }
  1718. }
  1719. handled = LLUICtrl::handleHover( x, y, mask );
  1720. return handled;
  1721. }
  1722. void LLScrollListCtrl::onMouseLeave(S32 x, S32 y, MASK mask)
  1723. {
  1724. // clear mouse highlight
  1725. mouseOverHighlightNthItem(-1);
  1726. }
  1727. BOOL LLScrollListCtrl::handleKeyHere(KEY key,MASK mask )
  1728. {
  1729. BOOL handled = FALSE;
  1730. // not called from parent means we have keyboard focus or a child does
  1731. if (mCanSelect) 
  1732. {
  1733. // Ignore capslock
  1734. mask = mask;
  1735. if (mask == MASK_NONE)
  1736. {
  1737. switch(key)
  1738. {
  1739. case KEY_UP:
  1740. if (mAllowKeyboardMovement || hasFocus())
  1741. {
  1742. // commit implicit in call
  1743. selectPrevItem(FALSE);
  1744. mNeedsScroll = TRUE;
  1745. handled = TRUE;
  1746. }
  1747. break;
  1748. case KEY_DOWN:
  1749. if (mAllowKeyboardMovement || hasFocus())
  1750. {
  1751. // commit implicit in call
  1752. selectNextItem(FALSE);
  1753. mNeedsScroll = TRUE;
  1754. handled = TRUE;
  1755. }
  1756. break;
  1757. case KEY_PAGE_UP:
  1758. if (mAllowKeyboardMovement || hasFocus())
  1759. {
  1760. selectNthItem(getFirstSelectedIndex() - (mScrollbar->getPageSize() - 1));
  1761. mNeedsScroll = TRUE;
  1762. if (mCommitOnKeyboardMovement
  1763. && !mCommitOnSelectionChange) 
  1764. {
  1765. onCommit();
  1766. }
  1767. handled = TRUE;
  1768. }
  1769. break;
  1770. case KEY_PAGE_DOWN:
  1771. if (mAllowKeyboardMovement || hasFocus())
  1772. {
  1773. selectNthItem(getFirstSelectedIndex() + (mScrollbar->getPageSize() - 1));
  1774. mNeedsScroll = TRUE;
  1775. if (mCommitOnKeyboardMovement
  1776. && !mCommitOnSelectionChange) 
  1777. {
  1778. onCommit();
  1779. }
  1780. handled = TRUE;
  1781. }
  1782. break;
  1783. case KEY_HOME:
  1784. if (mAllowKeyboardMovement || hasFocus())
  1785. {
  1786. selectFirstItem();
  1787. mNeedsScroll = TRUE;
  1788. if (mCommitOnKeyboardMovement
  1789. && !mCommitOnSelectionChange) 
  1790. {
  1791. onCommit();
  1792. }
  1793. handled = TRUE;
  1794. }
  1795. break;
  1796. case KEY_END:
  1797. if (mAllowKeyboardMovement || hasFocus())
  1798. {
  1799. selectNthItem(getItemCount() - 1);
  1800. mNeedsScroll = TRUE;
  1801. if (mCommitOnKeyboardMovement
  1802. && !mCommitOnSelectionChange) 
  1803. {
  1804. onCommit();
  1805. }
  1806. handled = TRUE;
  1807. }
  1808. break;
  1809. case KEY_RETURN:
  1810. // JC - Special case: Only claim to have handled it
  1811. // if we're the special non-commit-on-move
  1812. // type. AND we are visible
  1813.    if (!mCommitOnKeyboardMovement && mask == MASK_NONE)
  1814. {
  1815. onCommit();
  1816. mSearchString.clear();
  1817. handled = TRUE;
  1818. }
  1819. break;
  1820. case KEY_BACKSPACE:
  1821. mSearchTimer.reset();
  1822. if (mSearchString.size())
  1823. {
  1824. mSearchString.erase(mSearchString.size() - 1, 1);
  1825. }
  1826. if (mSearchString.empty())
  1827. {
  1828. if (getFirstSelected())
  1829. {
  1830. LLScrollListCell* cellp = getFirstSelected()->getColumn(getSearchColumn());
  1831. if (cellp)
  1832. {
  1833. cellp->highlightText(0, 0);
  1834. }
  1835. }
  1836. }
  1837. else if (selectItemByPrefix(wstring_to_utf8str(mSearchString), FALSE))
  1838. {
  1839. mNeedsScroll = TRUE;
  1840. // update search string only on successful match
  1841. mSearchTimer.reset();
  1842. if (mCommitOnKeyboardMovement
  1843. && !mCommitOnSelectionChange) 
  1844. {
  1845. onCommit();
  1846. }
  1847. }
  1848. break;
  1849. default:
  1850. break;
  1851. }
  1852. }
  1853. // TODO: multiple: shift-up, shift-down, shift-home, shift-end, select all
  1854. }
  1855. return handled;
  1856. }
  1857. BOOL LLScrollListCtrl::handleUnicodeCharHere(llwchar uni_char)
  1858. {
  1859. if ((uni_char < 0x20) || (uni_char == 0x7F)) // Control character or DEL
  1860. {
  1861. return FALSE;
  1862. }
  1863. // perform incremental search based on keyboard input
  1864. static LLUICachedControl<F32> type_ahead_timeout ("TypeAheadTimeout", 0);
  1865. if (mSearchTimer.getElapsedTimeF32() > type_ahead_timeout)
  1866. {
  1867. mSearchString.clear();
  1868. }
  1869. // type ahead search is case insensitive
  1870. uni_char = LLStringOps::toLower((llwchar)uni_char);
  1871. if (selectItemByPrefix(wstring_to_utf8str(mSearchString + (llwchar)uni_char), FALSE))
  1872. {
  1873. // update search string only on successful match
  1874. mNeedsScroll = TRUE;
  1875. mSearchString += uni_char;
  1876. mSearchTimer.reset();
  1877. if (mCommitOnKeyboardMovement
  1878. && !mCommitOnSelectionChange) 
  1879. {
  1880. onCommit();
  1881. }
  1882. }
  1883. // handle iterating over same starting character
  1884. else if (isRepeatedChars(mSearchString + (llwchar)uni_char) && !mItemList.empty())
  1885. {
  1886. // start from last selected item, in case we previously had a successful match against
  1887. // duplicated characters ('AA' matches 'Aaron')
  1888. item_list::iterator start_iter = mItemList.begin();
  1889. S32 first_selected = getFirstSelectedIndex();
  1890. // if we have a selection (> -1) then point iterator at the selected item
  1891. if (first_selected > 0)
  1892. {
  1893. // point iterator to first selected item
  1894. start_iter += first_selected;
  1895. }
  1896. // start search at first item after current selection
  1897. item_list::iterator iter = start_iter;
  1898. ++iter;
  1899. if (iter == mItemList.end())
  1900. {
  1901. iter = mItemList.begin();
  1902. }
  1903. // loop around once, back to previous selection
  1904. while(iter != start_iter)
  1905. {
  1906. LLScrollListItem* item = *iter;
  1907. LLScrollListCell* cellp = item->getColumn(getSearchColumn());
  1908. if (cellp)
  1909. {
  1910. // Only select enabled items with matching first characters
  1911. LLWString item_label = utf8str_to_wstring(cellp->getValue().asString());
  1912. if (item->getEnabled() && LLStringOps::toLower(item_label[0]) == uni_char)
  1913. {
  1914. selectItem(item);
  1915. mNeedsScroll = TRUE;
  1916. cellp->highlightText(0, 1);
  1917. mSearchTimer.reset();
  1918. if (mCommitOnKeyboardMovement
  1919. && !mCommitOnSelectionChange) 
  1920. {
  1921. onCommit();
  1922. }
  1923. break;
  1924. }
  1925. }
  1926. ++iter;
  1927. if (iter == mItemList.end())
  1928. {
  1929. iter = mItemList.begin();
  1930. }
  1931. }
  1932. }
  1933. return TRUE;
  1934. }
  1935. void LLScrollListCtrl::reportInvalidInput()
  1936. {
  1937. make_ui_sound("UISndBadKeystroke");
  1938. }
  1939. BOOL LLScrollListCtrl::isRepeatedChars(const LLWString& string) const
  1940. {
  1941. if (string.empty())
  1942. {
  1943. return FALSE;
  1944. }
  1945. llwchar first_char = string[0];
  1946. for (U32 i = 0; i < string.size(); i++)
  1947. {
  1948. if (string[i] != first_char)
  1949. {
  1950. return FALSE;
  1951. }
  1952. }
  1953. return TRUE;
  1954. }
  1955. void LLScrollListCtrl::selectItem(LLScrollListItem* itemp, BOOL select_single_item)
  1956. {
  1957. if (!itemp) return;
  1958. if (!itemp->getSelected())
  1959. {
  1960. if (mLastSelected)
  1961. {
  1962. LLScrollListCell* cellp = mLastSelected->getColumn(getSearchColumn());
  1963. if (cellp)
  1964. {
  1965. cellp->highlightText(0, 0);
  1966. }
  1967. }
  1968. if (select_single_item)
  1969. {
  1970. deselectAllItems(TRUE);
  1971. }
  1972. itemp->setSelected(TRUE);
  1973. mLastSelected = itemp;
  1974. mSelectionChanged = TRUE;
  1975. }
  1976. }
  1977. void LLScrollListCtrl::deselectItem(LLScrollListItem* itemp)
  1978. {
  1979. if (!itemp) return;
  1980. if (itemp->getSelected())
  1981. {
  1982. if (mLastSelected == itemp)
  1983. {
  1984. mLastSelected = NULL;
  1985. }
  1986. itemp->setSelected(FALSE);
  1987. LLScrollListCell* cellp = itemp->getColumn(getSearchColumn());
  1988. if (cellp)
  1989. {
  1990. cellp->highlightText(0, 0);
  1991. }
  1992. mSelectionChanged = TRUE;
  1993. }
  1994. }
  1995. void LLScrollListCtrl::commitIfChanged()
  1996. {
  1997. if (mSelectionChanged)
  1998. {
  1999. mDirty = TRUE;
  2000. mSelectionChanged = FALSE;
  2001. onCommit();
  2002. }
  2003. }
  2004. struct SameSortColumn
  2005. {
  2006. SameSortColumn(S32 column) : mColumn(column) {}
  2007. S32 mColumn;
  2008. bool operator()(std::pair<S32, BOOL> sort_column) { return sort_column.first == mColumn; }
  2009. };
  2010. BOOL LLScrollListCtrl::setSort(S32 column_idx, BOOL ascending)
  2011. {
  2012. LLScrollListColumn* sort_column = getColumn(column_idx);
  2013. if (!sort_column) return FALSE;
  2014. sort_column->mSortDirection = ascending ? LLScrollListColumn::ASCENDING : LLScrollListColumn::DESCENDING;
  2015. sort_column_t new_sort_column(column_idx, ascending);
  2016. setNeedsSort();
  2017. if (mSortColumns.empty())
  2018. {
  2019. mSortColumns.push_back(new_sort_column);
  2020. return TRUE;
  2021. }
  2022. else
  2023. {
  2024. // grab current sort column
  2025. sort_column_t cur_sort_column = mSortColumns.back();
  2026. // remove any existing sort criterion referencing this column
  2027. // and add the new one
  2028. mSortColumns.erase(remove_if(mSortColumns.begin(), mSortColumns.end(), SameSortColumn(column_idx)), mSortColumns.end()); 
  2029. mSortColumns.push_back(new_sort_column);
  2030. // did the sort criteria change?
  2031. return (cur_sort_column != new_sort_column);
  2032. }
  2033. }
  2034. S32 LLScrollListCtrl::getLinesPerPage()
  2035. {
  2036. //if mPageLines is NOT provided display all item
  2037. if(mPageLines)
  2038. {
  2039. return mPageLines;
  2040. }
  2041. else
  2042. {
  2043. return mLineHeight ? mItemListRect.getHeight() / mLineHeight : getItemCount();
  2044. }
  2045. }
  2046. // Called by scrollbar
  2047. void LLScrollListCtrl::onScrollChange( S32 new_pos, LLScrollbar* scrollbar )
  2048. {
  2049. mScrollLines = new_pos;
  2050. }
  2051. void LLScrollListCtrl::sortByColumn(const std::string& name, BOOL ascending)
  2052. {
  2053. std::map<std::string, LLScrollListColumn>::iterator itor = mColumns.find(name);
  2054. if (itor != mColumns.end())
  2055. {
  2056. sortByColumnIndex((*itor).second.mIndex, ascending);
  2057. }
  2058. }
  2059. // First column is column 0
  2060. void  LLScrollListCtrl::sortByColumnIndex(U32 column, BOOL ascending)
  2061. {
  2062. setSort(column, ascending);
  2063. updateSort();
  2064. }
  2065. void LLScrollListCtrl::updateSort() const
  2066. {
  2067. if (hasSortOrder() && !isSorted())
  2068. {
  2069. // do stable sort to preserve any previous sorts
  2070. std::stable_sort(
  2071. mItemList.begin(), 
  2072. mItemList.end(), 
  2073. SortScrollListItem(mSortColumns));
  2074. mSorted = true;
  2075. }
  2076. }
  2077. // for one-shot sorts, does not save sort column/order
  2078. void LLScrollListCtrl::sortOnce(S32 column, BOOL ascending)
  2079. {
  2080. std::vector<std::pair<S32, BOOL> > sort_column;
  2081. sort_column.push_back(std::make_pair(column, ascending));
  2082. // do stable sort to preserve any previous sorts
  2083. std::stable_sort(
  2084. mItemList.begin(), 
  2085. mItemList.end(), 
  2086. SortScrollListItem(sort_column));
  2087. }
  2088. void LLScrollListCtrl::dirtyColumns() 
  2089. mColumnsDirty = TRUE; 
  2090. // need to keep mColumnsIndexed up to date
  2091. // just in case someone indexes into it immediately
  2092. mColumnsIndexed.resize(mColumns.size());
  2093. std::map<std::string, LLScrollListColumn>::iterator column_itor;
  2094. for (column_itor = mColumns.begin(); column_itor != mColumns.end(); ++column_itor)
  2095. {
  2096. LLScrollListColumn *column = &column_itor->second;
  2097. mColumnsIndexed[column_itor->second.mIndex] = column;
  2098. }
  2099. }
  2100. S32 LLScrollListCtrl::getScrollPos() const
  2101. {
  2102. return mScrollbar->getDocPos();
  2103. }
  2104. void LLScrollListCtrl::setScrollPos( S32 pos )
  2105. {
  2106. mScrollbar->setDocPos( pos );
  2107. onScrollChange(mScrollbar->getDocPos(), mScrollbar);
  2108. }
  2109. void LLScrollListCtrl::scrollToShowSelected()
  2110. {
  2111. // don't scroll automatically when capturing mouse input
  2112. // as that will change what is currently under the mouse cursor
  2113. if (hasMouseCapture())
  2114. {
  2115. return;
  2116. }
  2117. updateSort();
  2118. S32 index = getFirstSelectedIndex();
  2119. if (index < 0)
  2120. {
  2121. return;
  2122. }
  2123. LLScrollListItem* item = mItemList[index];
  2124. if (!item)
  2125. {
  2126. // I don't THINK this should ever happen.
  2127. return;
  2128. }
  2129. S32 lowest = mScrollLines;
  2130. S32 page_lines = getLinesPerPage();
  2131. S32 highest = mScrollLines + page_lines;
  2132. if (index < lowest)
  2133. {
  2134. // need to scroll to show item
  2135. setScrollPos(index);
  2136. }
  2137. else if (highest <= index)
  2138. {
  2139. setScrollPos(index - page_lines + 1);
  2140. }
  2141. }
  2142. void LLScrollListCtrl::updateStaticColumnWidth(LLScrollListColumn* col, S32 new_width)
  2143. {
  2144. mTotalStaticColumnWidth += llmax(0, new_width) - llmax(0, col->getWidth());
  2145. }
  2146. // LLEditMenuHandler functions
  2147. // virtual
  2148. void LLScrollListCtrl::copy()
  2149. {
  2150. std::string buffer;
  2151. std::vector<LLScrollListItem*> items = getAllSelected();
  2152. std::vector<LLScrollListItem*>::iterator itor;
  2153. for (itor = items.begin(); itor != items.end(); ++itor)
  2154. {
  2155. buffer += (*itor)->getContentsCSV() + "n";
  2156. }
  2157. gClipboard.copyFromSubstring(utf8str_to_wstring(buffer), 0, buffer.length());
  2158. }
  2159. // virtual
  2160. BOOL LLScrollListCtrl::canCopy() const
  2161. {
  2162. return (getFirstSelected() != NULL);
  2163. }
  2164. // virtual
  2165. void LLScrollListCtrl::cut()
  2166. {
  2167. copy();
  2168. doDelete();
  2169. }
  2170. // virtual
  2171. BOOL LLScrollListCtrl::canCut() const
  2172. {
  2173. return canCopy() && canDoDelete();
  2174. }
  2175. // virtual
  2176. void LLScrollListCtrl::selectAll()
  2177. {
  2178. // Deselects all other items
  2179. item_list::iterator iter;
  2180. for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
  2181. {
  2182. LLScrollListItem *itemp = *iter;
  2183. if( itemp->getEnabled() )
  2184. {
  2185. selectItem(itemp, FALSE);
  2186. }
  2187. }
  2188. if (mCommitOnSelectionChange)
  2189. {
  2190. commitIfChanged();
  2191. }
  2192. }
  2193. // virtual
  2194. BOOL LLScrollListCtrl::canSelectAll() const
  2195. {
  2196. return getCanSelect() && mAllowMultipleSelection && !(mMaxSelectable > 0 && mItemList.size() > mMaxSelectable);
  2197. }
  2198. // virtual
  2199. void LLScrollListCtrl::deselect()
  2200. {
  2201. deselectAllItems();
  2202. }
  2203. // virtual
  2204. BOOL LLScrollListCtrl::canDeselect() const
  2205. {
  2206. return getCanSelect();
  2207. }
  2208. void LLScrollListCtrl::addColumn(const LLSD& column, EAddPosition pos)
  2209. {
  2210. LLScrollListColumn::Params p;
  2211. LLParamSDParser::instance().readSD(column, p);
  2212. addColumn(p, pos);
  2213. }
  2214. void LLScrollListCtrl::addColumn(const LLScrollListColumn::Params& column_params, EAddPosition pos)
  2215. {
  2216. if (!column_params.validateBlock()) return;
  2217. std::string name = column_params.name;
  2218. // if no column name provided, just use ordinal as name
  2219. if (name.empty())
  2220. {
  2221. name = llformat("%d", mColumnsIndexed.size());
  2222. }
  2223. if (mColumns.find(name) == mColumns.end())
  2224. {
  2225. // Add column
  2226. mColumns[name] = LLScrollListColumn(column_params, this);
  2227. LLScrollListColumn* new_column = &mColumns[name];
  2228. new_column->mIndex = mColumns.size()-1;
  2229. // Add button
  2230. if (new_column->getWidth() > 0 || new_column->mRelWidth > 0 || new_column->mDynamicWidth)
  2231. {
  2232. if (getNumColumns() > 0)
  2233. {
  2234. mTotalColumnPadding += mColumnPadding;
  2235. }
  2236. if (new_column->mRelWidth >= 0)
  2237. {
  2238. new_column->setWidth((S32)llround(new_column->mRelWidth*mItemListRect.getWidth()));
  2239. }
  2240. else if(new_column->mDynamicWidth)
  2241. {
  2242. mNumDynamicWidthColumns++;
  2243. new_column->setWidth((mItemListRect.getWidth() - mTotalStaticColumnWidth - mTotalColumnPadding) / mNumDynamicWidthColumns);
  2244. }
  2245. S32 top = mItemListRect.mTop;
  2246. S32 left = mItemListRect.mLeft;
  2247. for (std::map<std::string, LLScrollListColumn>::iterator itor = mColumns.begin(); 
  2248. itor != mColumns.end(); 
  2249. ++itor)
  2250. {
  2251. if (itor->second.mIndex < new_column->mIndex &&
  2252. itor->second.getWidth() > 0)
  2253. {
  2254. left += itor->second.getWidth() + mColumnPadding;
  2255. }
  2256. }
  2257. S32 right = left+new_column->getWidth();
  2258. if (new_column->mIndex != (S32)mColumns.size()-1)
  2259. {
  2260. right += mColumnPadding;
  2261. }
  2262. LLRect temp_rect = LLRect(left,top+mHeadingHeight,right,top);
  2263. LLScrollColumnHeader::Params params(LLUICtrlFactory::getDefaultParams<LLScrollColumnHeader>());
  2264. params.name = "btn_" + name;
  2265. params.rect = temp_rect;
  2266. params.column = new_column;
  2267. params.tool_tip = column_params.tool_tip;
  2268. params.tab_stop = false;
  2269. params.visible = mDisplayColumnHeaders;
  2270. if(column_params.header.image.isProvided())
  2271. {
  2272. params.image_selected = column_params.header.image;
  2273. params.image_unselected = column_params.header.image;
  2274. }
  2275. else
  2276. {
  2277. params.label = column_params.header.label;
  2278. }
  2279. new_column->mHeader = LLUICtrlFactory::create<LLScrollColumnHeader>(params); 
  2280. addChild(new_column->mHeader);
  2281. sendChildToFront(mScrollbar);
  2282. }
  2283. }
  2284. dirtyColumns();
  2285. }
  2286. // static
  2287. void LLScrollListCtrl::onClickColumn(void *userdata)
  2288. {
  2289. LLScrollListColumn *info = (LLScrollListColumn*)userdata;
  2290. if (!info) return;
  2291. LLScrollListCtrl *parent = info->mParentCtrl;
  2292. if (!parent) return;
  2293. S32 column_index = info->mIndex;
  2294. LLScrollListColumn* column = parent->mColumnsIndexed[info->mIndex];
  2295. bool ascending = column->mSortDirection == LLScrollListColumn::ASCENDING;
  2296. if (column->mSortingColumn != column->mName
  2297. && parent->mColumns.find(column->mSortingColumn) != parent->mColumns.end())
  2298. {
  2299. LLScrollListColumn& info_redir = parent->mColumns[column->mSortingColumn];
  2300. column_index = info_redir.mIndex;
  2301. }
  2302. // if this column is the primary sort key, reverse the direction
  2303. sort_column_t cur_sort_column;
  2304. if (!parent->mSortColumns.empty() && parent->mSortColumns.back().first == column_index)
  2305. {
  2306. ascending = !parent->mSortColumns.back().second;
  2307. }
  2308. parent->sortByColumnIndex(column_index, ascending);
  2309. if (parent->mOnSortChangedCallback)
  2310. {
  2311. parent->mOnSortChangedCallback();
  2312. }
  2313. }
  2314. std::string LLScrollListCtrl::getSortColumnName()
  2315. {
  2316. LLScrollListColumn* column = mSortColumns.empty() ? NULL : mColumnsIndexed[mSortColumns.back().first];
  2317. if (column) return column->mName;
  2318. else return "";
  2319. }
  2320. BOOL LLScrollListCtrl::hasSortOrder() const
  2321. {
  2322. return !mSortColumns.empty();
  2323. }
  2324. void LLScrollListCtrl::clearColumns()
  2325. {
  2326. std::map<std::string, LLScrollListColumn>::iterator itor;
  2327. for (itor = mColumns.begin(); itor != mColumns.end(); ++itor)
  2328. {
  2329. LLScrollColumnHeader *header = itor->second.mHeader;
  2330. if (header)
  2331. {
  2332. removeChild(header);
  2333. delete header;
  2334. }
  2335. }
  2336. mColumns.clear();
  2337. mSortColumns.clear();
  2338. mTotalStaticColumnWidth = 0;
  2339. mTotalColumnPadding = 0;
  2340. }
  2341. void LLScrollListCtrl::setColumnLabel(const std::string& column, const std::string& label)
  2342. {
  2343. LLScrollListColumn* columnp = getColumn(column);
  2344. if (columnp)
  2345. {
  2346. columnp->mLabel = label;
  2347. if (columnp->mHeader)
  2348. {
  2349. columnp->mHeader->setLabel(label);
  2350. }
  2351. }
  2352. }
  2353. LLScrollListColumn* LLScrollListCtrl::getColumn(S32 index)
  2354. {
  2355. if (index < 0 || index >= (S32)mColumnsIndexed.size())
  2356. {
  2357. return NULL;
  2358. }
  2359. return mColumnsIndexed[index];
  2360. }
  2361. LLScrollListColumn* LLScrollListCtrl::getColumn(const std::string& name)
  2362. {
  2363. column_map_t::iterator column_itor = mColumns.find(name);
  2364. if (column_itor != mColumns.end()) 
  2365. {
  2366. return &column_itor->second;
  2367. }
  2368. return NULL;
  2369. }
  2370. LLScrollListItem* LLScrollListCtrl::addElement(const LLSD& element, EAddPosition pos, void* userdata)
  2371. {
  2372. LLScrollListItem::Params item_params;
  2373. LLParamSDParser::instance().readSD(element, item_params);
  2374. item_params.userdata = userdata;
  2375. return addRow(item_params, pos);
  2376. }
  2377. LLScrollListItem* LLScrollListCtrl::addRow(const LLScrollListItem::Params& item_p, EAddPosition pos)
  2378. {
  2379. LLScrollListItem *new_item = new LLScrollListItem(item_p);
  2380. return addRow(new_item, item_p, pos);
  2381. }
  2382. LLScrollListItem* LLScrollListCtrl::addRow(LLScrollListItem *new_item, const LLScrollListItem::Params& item_p, EAddPosition pos)
  2383. {
  2384. if (!item_p.validateBlock() || !new_item) return NULL;
  2385. new_item->setNumColumns(mColumns.size());
  2386. // Add any columns we don't already have
  2387. S32 col_index = 0;
  2388. for(LLInitParam::ParamIterator<LLScrollListCell::Params>::const_iterator itor = item_p.columns().begin();
  2389. itor != item_p.columns().end();
  2390. ++itor)
  2391. {
  2392. LLScrollListCell::Params cell_p = *itor;
  2393. std::string column = cell_p.column;
  2394. // empty columns strings index by ordinal
  2395. if (column.empty())
  2396. {
  2397. column = llformat("%d", col_index);
  2398. }
  2399. LLScrollListColumn* columnp = getColumn(column);
  2400. // create new column on demand
  2401. if (!columnp)
  2402. {
  2403. LLScrollListColumn::Params new_column;
  2404. new_column.name = column;
  2405. new_column.header.label = column;
  2406. // if width supplied for column, use it, otherwise 
  2407. // use adaptive width
  2408. if (cell_p.width.isProvided())
  2409. {
  2410. new_column.width.pixel_width = cell_p.width;
  2411. }
  2412. addColumn(new_column);
  2413. columnp = &mColumns[column];
  2414. new_item->setNumColumns(mColumns.size());
  2415. }
  2416. S32 index = columnp->mIndex;
  2417. cell_p.width.setIfNotProvided(columnp->getWidth());
  2418. LLScrollListCell* cell = LLScrollListCell::create(cell_p);
  2419. if (cell)
  2420. {
  2421. new_item->setColumn(index, cell);
  2422. if (columnp->mHeader 
  2423. && cell->isText() 
  2424. && !cell->getValue().asString().empty())
  2425. {
  2426. columnp->mHeader->setHasResizableElement(TRUE);
  2427. }
  2428. }
  2429. col_index++;
  2430. }
  2431. if (item_p.columns().empty())
  2432. {
  2433. if (mColumns.empty())
  2434. {
  2435. LLScrollListColumn::Params new_column;
  2436. new_column.name = "0";
  2437. addColumn(new_column);
  2438. new_item->setNumColumns(mColumns.size());
  2439. }
  2440. LLScrollListCell* cell = LLScrollListCell::create(LLScrollListCell::Params().value(item_p.value));
  2441. if (cell)
  2442. {
  2443. LLScrollListColumn* columnp = &(mColumns.begin()->second);
  2444. new_item->setColumn(0, cell);
  2445. if (columnp->mHeader 
  2446. && cell->isText() 
  2447. && !cell->getValue().asString().empty())
  2448. {
  2449. columnp->mHeader->setHasResizableElement(TRUE);
  2450. }
  2451. }
  2452. }
  2453. // add dummy cells for missing columns
  2454. for (column_map_t::iterator column_it = mColumns.begin(); column_it != mColumns.end(); ++column_it)
  2455. {
  2456. S32 column_idx = column_it->second.mIndex;
  2457. if (new_item->getColumn(column_idx) == NULL)
  2458. {
  2459. LLScrollListColumn* column_ptr = &column_it->second;
  2460. LLScrollListCell::Params cell_p;
  2461. cell_p.width = column_ptr->getWidth();
  2462. new_item->setColumn(column_idx, new LLScrollListSpacer(cell_p));
  2463. }
  2464. }
  2465. addItem(new_item, pos);
  2466. return new_item;
  2467. }
  2468. LLScrollListItem* LLScrollListCtrl::addSimpleElement(const std::string& value, EAddPosition pos, const LLSD& id)
  2469. {
  2470. LLSD entry_id = id;
  2471. if (id.isUndefined())
  2472. {
  2473. entry_id = value;
  2474. }
  2475. LLScrollListItem::Params item_params;
  2476. item_params.value(entry_id);
  2477. item_params.columns.add()
  2478. .value(value)
  2479. .font(LLFontGL::getFontSansSerifSmall());
  2480. return addRow(item_params, pos);
  2481. }
  2482. void LLScrollListCtrl::setValue(const LLSD& value )
  2483. {
  2484. LLSD::array_const_iterator itor;
  2485. for (itor = value.beginArray(); itor != value.endArray(); ++itor)
  2486. {
  2487. addElement(*itor);
  2488. }
  2489. }
  2490. LLSD LLScrollListCtrl::getValue() const
  2491. {
  2492. LLScrollListItem *item = getFirstSelected();
  2493. if (!item) return LLSD();
  2494. return item->getValue();
  2495. }
  2496. BOOL LLScrollListCtrl::operateOnSelection(EOperation op)
  2497. {
  2498. if (op == OP_DELETE)
  2499. {
  2500. deleteSelectedItems();
  2501. return TRUE;
  2502. }
  2503. else if (op == OP_DESELECT)
  2504. {
  2505. deselectAllItems();
  2506. }
  2507. return FALSE;
  2508. }
  2509. BOOL LLScrollListCtrl::operateOnAll(EOperation op)
  2510. {
  2511. if (op == OP_DELETE)
  2512. {
  2513. clearRows();
  2514. return TRUE;
  2515. }
  2516. else if (op == OP_DESELECT)
  2517. {
  2518. deselectAllItems();
  2519. }
  2520. else if (op == OP_SELECT)
  2521. {
  2522. selectAll();
  2523. }
  2524. return FALSE;
  2525. }
  2526. //virtual 
  2527. void LLScrollListCtrl::setFocus(BOOL b)
  2528. {
  2529. mSearchString.clear();
  2530. // for tabbing into pristine scroll lists (Finder)
  2531. if (!getFirstSelected())
  2532. {
  2533. selectFirstItem();
  2534. //onCommit(); // SJB: selectFirstItem() will call onCommit() if appropriate
  2535. }
  2536. LLUICtrl::setFocus(b);
  2537. }
  2538. // virtual 
  2539. BOOL LLScrollListCtrl::isDirty() const
  2540. {
  2541. BOOL grubby = mDirty;
  2542. if ( !mAllowMultipleSelection )
  2543. {
  2544. grubby = (mOriginalSelection != getFirstSelectedIndex());
  2545. }
  2546. return grubby;
  2547. }
  2548. // Clear dirty state
  2549. void LLScrollListCtrl::resetDirty()
  2550. {
  2551. mDirty = FALSE;
  2552. mOriginalSelection = getFirstSelectedIndex();
  2553. }
  2554. //virtual
  2555. void LLScrollListCtrl::onFocusReceived()
  2556. {
  2557. // forget latent selection changes when getting focus
  2558. mSelectionChanged = FALSE;
  2559. LLUICtrl::onFocusReceived();
  2560. }
  2561. //virtual 
  2562. void LLScrollListCtrl::onFocusLost()
  2563. {
  2564. if (hasMouseCapture())
  2565. {
  2566. gFocusMgr.setMouseCapture(NULL);
  2567. }
  2568. LLUICtrl::onFocusLost();
  2569. }