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

/indra/llui/llflatlistview.cpp

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