PageRenderTime 51ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 1ms

/indra/llui/llscrolllistctrl.cpp

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