ListView.cpp 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698
  1. //
  2. // Urho3D Engine
  3. // Copyright (c) 2008-2011 Lasse Öörni
  4. //
  5. // Permission is hereby granted, free of charge, to any person obtaining a copy
  6. // of this software and associated documentation files (the "Software"), to deal
  7. // in the Software without restriction, including without limitation the rights
  8. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  9. // copies of the Software, and to permit persons to whom the Software is
  10. // furnished to do so, subject to the following conditions:
  11. //
  12. // The above copyright notice and this permission notice shall be included in
  13. // all copies or substantial portions of the Software.
  14. //
  15. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  16. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  17. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  18. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  19. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  20. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  21. // THE SOFTWARE.
  22. //
  23. #include "Precompiled.h"
  24. #include "BorderImage.h"
  25. #include "InputEvents.h"
  26. #include "ListView.h"
  27. #include "Log.h"
  28. #include "StringUtils.h"
  29. #include "UIEvents.h"
  30. #include "DebugNew.h"
  31. static const ShortStringHash indentHash("Indent");
  32. static const std::string highlightModes[] =
  33. {
  34. "never",
  35. "focus"
  36. "always"
  37. };
  38. int getItemIndent(UIElement* item)
  39. {
  40. if (!item)
  41. return 0;
  42. return item->getUserData()[indentHash].getInt();
  43. }
  44. ListView::ListView(const std::string& name) :
  45. ScrollView(name),
  46. mHighlightMode(HM_FOCUS),
  47. mMultiselect(false),
  48. mHierarchyMode(false),
  49. mDoubleClickInterval(0.5f),
  50. mDoubleClickTimer(0.0f),
  51. mLastClickedItem(M_MAX_UNSIGNED)
  52. {
  53. UIElement* container = new UIElement();
  54. container->setEnabled(true);
  55. container->setLayout(LM_VERTICAL);
  56. setContentElement(container);
  57. subscribeToEvent(EVENT_UIMOUSECLICK, EVENT_HANDLER(ListView, handleUIMouseClick));
  58. }
  59. ListView::~ListView()
  60. {
  61. }
  62. void ListView::setStyle(const XMLElement& element, ResourceCache* cache)
  63. {
  64. ScrollView::setStyle(element, cache);
  65. UIElement* root = getRootElement();
  66. XMLElement itemElem = element.getChildElement("listitem");
  67. if (root)
  68. {
  69. while (itemElem)
  70. {
  71. if (itemElem.hasAttribute("name"))
  72. {
  73. UIElement* item = root->getChild(itemElem.getString("name"), true);
  74. addItem(item);
  75. if (itemElem.hasAttribute("indent"))
  76. item->getUserData()[indentHash] = itemElem.getInt("indent");
  77. itemElem = itemElem.getNextElement("listitem");
  78. }
  79. }
  80. }
  81. if (element.hasChildElement("highlightmode"))
  82. {
  83. std::string highlightMode = element.getChildElement("highlightmode").getStringLower("value");
  84. setHighlightMode((HighlightMode)getIndexFromStringList(highlightMode, highlightModes, 3, 1));
  85. }
  86. if (element.hasChildElement("multiselect"))
  87. setMultiselect(element.getChildElement("multiselect").getBool("enable"));
  88. if (element.hasChildElement("hierarchymode"))
  89. setHierarchyMode(element.getChildElement("hierarchymode").getBool("enable"));
  90. if (element.hasChildElement("doubleclickinterval"))
  91. setDoubleClickInterval(element.getChildElement("doubleclickinterval").getFloat("value"));
  92. XMLElement selectionElem = element.getChildElement("selection");
  93. while (selectionElem)
  94. {
  95. addSelection(selectionElem.getInt("value"));
  96. selectionElem = selectionElem.getNextElement("selection");
  97. }
  98. }
  99. void ListView::update(float timeStep)
  100. {
  101. if (mDoubleClickTimer > 0.0f)
  102. mDoubleClickTimer = max(mDoubleClickTimer - timeStep, 0.0f);
  103. }
  104. void ListView::onKey(int key, int buttons, int qualifiers)
  105. {
  106. // If no selection, can not move with keys
  107. unsigned numItems = getNumItems();
  108. unsigned selection = getSelection();
  109. if ((selection != M_MAX_UNSIGNED) && (numItems))
  110. {
  111. // If either shift or ctrl held down, add to selection if multiselect enabled
  112. bool additive = (mMultiselect) && (qualifiers != 0);
  113. switch (key)
  114. {
  115. case KEY_LEFT:
  116. if (mHierarchyMode)
  117. {
  118. setChildItemsVisible(selection, false);
  119. return;
  120. }
  121. break;
  122. case KEY_RIGHT:
  123. if (mHierarchyMode)
  124. {
  125. setChildItemsVisible(selection, true);
  126. return;
  127. }
  128. break;
  129. case KEY_RETURN:
  130. if (mHierarchyMode)
  131. {
  132. toggleChildItemsVisible(selection);
  133. return;
  134. }
  135. break;
  136. case KEY_UP:
  137. changeSelection(-1, additive);
  138. return;
  139. case KEY_DOWN:
  140. changeSelection(1, additive);
  141. return;
  142. case KEY_PAGEUP:
  143. {
  144. // Convert page step to pixels and see how many items we have to skip to reach that many pixels
  145. int stepPixels = ((int)(mPageStep * mScrollPanel->getHeight())) - getSelectedItem()->getHeight();
  146. unsigned newSelection = selection;
  147. unsigned okSelection = selection;
  148. while (newSelection < numItems)
  149. {
  150. UIElement* item = getItem(newSelection);
  151. int height = 0;
  152. if (item->isVisible())
  153. {
  154. height = item->getHeight();
  155. okSelection = newSelection;
  156. }
  157. if (stepPixels < height)
  158. break;
  159. stepPixels -= height;
  160. --newSelection;
  161. }
  162. if (!additive)
  163. setSelection(okSelection);
  164. else
  165. addSelection(okSelection);
  166. }
  167. return;
  168. case KEY_PAGEDOWN:
  169. {
  170. int stepPixels = ((int)(mPageStep * mScrollPanel->getHeight())) - getSelectedItem()->getHeight();
  171. unsigned newSelection = selection;
  172. unsigned okSelection = selection;
  173. while (newSelection < numItems)
  174. {
  175. UIElement* item = getItem(newSelection);
  176. int height = 0;
  177. if (item->isVisible())
  178. {
  179. height = item->getHeight();
  180. okSelection = newSelection;
  181. }
  182. if (stepPixels < height)
  183. break;
  184. stepPixels -= height;
  185. ++newSelection;
  186. }
  187. if (!additive)
  188. setSelection(okSelection);
  189. else
  190. addSelection(okSelection);
  191. }
  192. return;
  193. case KEY_HOME:
  194. changeSelection(-(int)getNumItems(), additive);
  195. return;
  196. case KEY_END:
  197. changeSelection(getNumItems(), additive);
  198. return;
  199. }
  200. }
  201. using namespace ListViewKey;
  202. VariantMap eventData;
  203. eventData[P_ELEMENT] = (void*)this;
  204. eventData[P_KEY] = key;
  205. eventData[P_BUTTONS] = buttons;
  206. eventData[P_QUALIFIERS] = qualifiers;
  207. sendEvent(EVENT_LISTVIEWKEY, eventData);
  208. }
  209. void ListView::onResize()
  210. {
  211. ScrollView::onResize();
  212. // Set the content element width to match the scrollpanel
  213. const IntRect& clipBorder = mScrollPanel->getClipBorder();
  214. mContentElement->setWidth(mScrollPanel->getWidth() - clipBorder.mLeft - clipBorder.mRight);
  215. }
  216. void ListView::onFocus()
  217. {
  218. updateSelectionEffect();
  219. }
  220. void ListView::onDefocus()
  221. {
  222. updateSelectionEffect();
  223. }
  224. void ListView::addItem(UIElement* item)
  225. {
  226. if ((!item) || (item->getParent() == mContentElement))
  227. return;
  228. // Enable input so that clicking the item can be detected
  229. item->setEnabled(true);
  230. item->setSelected(false);
  231. mContentElement->addChild(item);
  232. }
  233. void ListView::removeItem(UIElement* item)
  234. {
  235. unsigned numItems = getNumItems();
  236. for (unsigned i = 0; i < numItems; ++i)
  237. {
  238. if (getItem(i) == item)
  239. {
  240. item->setSelected(false);
  241. mSelections.erase(i);
  242. // Remove any child items in hierarchy mode
  243. unsigned removed = 1;
  244. if (mHierarchyMode)
  245. {
  246. int baseIndent = getItemIndent(item);
  247. for (unsigned j = i + 1; j < numItems; ++j)
  248. {
  249. UIElement* childItem = getItem(j);
  250. if (getItemIndent(childItem) > baseIndent)
  251. {
  252. childItem->setSelected(false);
  253. mSelections.erase(j);
  254. mContentElement->removeChild(childItem);
  255. ++removed;
  256. }
  257. else
  258. break;
  259. }
  260. }
  261. // If necessary, shift the following selections
  262. std::set<unsigned> prevSelections;
  263. mSelections.clear();
  264. for (std::set<unsigned>::iterator j = prevSelections.begin(); j != prevSelections.end(); ++j)
  265. {
  266. if (*j > i)
  267. mSelections.insert(*j - removed);
  268. else
  269. mSelections.insert(*j);
  270. }
  271. updateSelectionEffect();
  272. break;
  273. }
  274. }
  275. mContentElement->removeChild(item);
  276. }
  277. void ListView::removeItem(unsigned index)
  278. {
  279. removeItem(getItem(index));
  280. }
  281. void ListView::removeAllItems()
  282. {
  283. unsigned numItems = getNumItems();
  284. for (unsigned i = 0; i < numItems; ++i)
  285. mContentElement->getChild(i)->setSelected(false);
  286. mContentElement->removeAllChildren();
  287. clearSelection();
  288. }
  289. void ListView::setSelection(unsigned index)
  290. {
  291. std::set<unsigned> indices;
  292. indices.insert(index);
  293. setSelections(indices);
  294. ensureItemVisibility(index);
  295. }
  296. void ListView::setSelections(const std::set<unsigned>& indices)
  297. {
  298. unsigned numItems = getNumItems();
  299. // Remove first items that should no longer be selected
  300. for (std::set<unsigned>::iterator i = mSelections.begin(); i != mSelections.end();)
  301. {
  302. unsigned index = *i;
  303. if (indices.find(index) == indices.end())
  304. {
  305. i = mSelections.erase(i);
  306. using namespace ItemSelected;
  307. VariantMap eventData;
  308. eventData[P_ELEMENT] = (void*)this;
  309. eventData[P_SELECTION] = index;
  310. sendEvent(EVENT_ITEMDESELECTED, eventData);
  311. }
  312. else
  313. ++i;
  314. }
  315. // Then add missing items
  316. for (std::set<unsigned>::const_iterator i = indices.begin(); i != indices.end(); ++i)
  317. {
  318. unsigned index = *i;
  319. if (index < numItems)
  320. {
  321. // In singleselect mode, resend the event even for the same selection
  322. if ((mSelections.find(index) == mSelections.end()) || (!mMultiselect))
  323. {
  324. mSelections.insert(*i);
  325. using namespace ItemSelected;
  326. VariantMap eventData;
  327. eventData[P_ELEMENT] = (void*)this;
  328. eventData[P_SELECTION] = *i;
  329. sendEvent(EVENT_ITEMSELECTED, eventData);
  330. }
  331. }
  332. // If no multiselect enabled, allow setting only one item
  333. if (!mMultiselect)
  334. break;
  335. }
  336. updateSelectionEffect();
  337. }
  338. void ListView::addSelection(unsigned index)
  339. {
  340. if (!mMultiselect)
  341. setSelection(index);
  342. else
  343. {
  344. if (index >= getNumItems())
  345. return;
  346. std::set<unsigned> newSelections = mSelections;
  347. newSelections.insert(index);
  348. setSelections(newSelections);
  349. ensureItemVisibility(index);
  350. }
  351. }
  352. void ListView::removeSelection(unsigned index)
  353. {
  354. if (index >= getNumItems())
  355. return;
  356. std::set<unsigned> newSelections = mSelections;
  357. newSelections.erase(index);
  358. setSelections(newSelections);
  359. ensureItemVisibility(index);
  360. }
  361. void ListView::toggleSelection(unsigned index)
  362. {
  363. unsigned numItems = getNumItems();
  364. if (index >= numItems)
  365. return;
  366. if (mSelections.find(index) != mSelections.end())
  367. removeSelection(index);
  368. else
  369. addSelection(index);
  370. }
  371. void ListView::changeSelection(int delta, bool additive)
  372. {
  373. if (mSelections.empty())
  374. return;
  375. if (!mMultiselect)
  376. additive = false;
  377. // If going downwards, use the last selection as a base. Otherwise use first
  378. unsigned selection = delta > 0 ? *mSelections.rbegin() : *mSelections.begin();
  379. unsigned numItems = getNumItems();
  380. unsigned newSelection = selection;
  381. unsigned okSelection = selection;
  382. while (delta != 0)
  383. {
  384. if (delta > 0)
  385. {
  386. ++newSelection;
  387. if (newSelection >= numItems)
  388. break;
  389. }
  390. if (delta < 0)
  391. {
  392. --newSelection;
  393. if (newSelection >= numItems)
  394. break;
  395. }
  396. UIElement* item = getItem(newSelection);
  397. if (item->isVisible())
  398. {
  399. okSelection = newSelection;
  400. if (delta > 0)
  401. --delta;
  402. if (delta < 0)
  403. ++delta;
  404. }
  405. }
  406. if (!additive)
  407. setSelection(okSelection);
  408. else
  409. addSelection(okSelection);
  410. }
  411. void ListView::clearSelection()
  412. {
  413. setSelections(std::set<unsigned>());
  414. updateSelectionEffect();
  415. }
  416. void ListView::setHighlightMode(HighlightMode mode)
  417. {
  418. mHighlightMode = mode;
  419. updateSelectionEffect();
  420. }
  421. void ListView::setMultiselect(bool enable)
  422. {
  423. mMultiselect = enable;
  424. }
  425. void ListView::setHierarchyMode(bool enable)
  426. {
  427. mHierarchyMode = enable;
  428. }
  429. void ListView::setDoubleClickInterval(float interval)
  430. {
  431. mDoubleClickInterval = interval;
  432. }
  433. void ListView::setChildItemsVisible(unsigned index, bool enable)
  434. {
  435. unsigned numItems = getNumItems();
  436. if ((!mHierarchyMode) || (index >= numItems))
  437. return;
  438. int baseIndent = getItemIndent(getItem(index));
  439. for (unsigned i = index + 1; i < numItems; ++i)
  440. {
  441. UIElement* item = getItem(i);
  442. if (getItemIndent(item) > baseIndent)
  443. item->setVisible(enable);
  444. else
  445. break;
  446. }
  447. }
  448. void ListView::toggleChildItemsVisible(unsigned index)
  449. {
  450. unsigned numItems = getNumItems();
  451. if ((!mHierarchyMode) || (index >= numItems))
  452. return;
  453. int baseIndent = getItemIndent(getItem(index));
  454. for (unsigned i = index + 1; i < numItems; ++i)
  455. {
  456. UIElement* item = getItem(i);
  457. if (getItemIndent(item) > baseIndent)
  458. item->setVisible(!item->isVisible());
  459. else
  460. break;
  461. }
  462. }
  463. unsigned ListView::getNumItems() const
  464. {
  465. return mContentElement->getNumChildren();
  466. }
  467. UIElement* ListView::getItem(unsigned index) const
  468. {
  469. return mContentElement->getChild(index);
  470. }
  471. std::vector<UIElement*> ListView::getItems() const
  472. {
  473. return mContentElement->getChildren();
  474. }
  475. unsigned ListView::getSelection() const
  476. {
  477. if (mSelections.empty())
  478. return M_MAX_UNSIGNED;
  479. else
  480. return *mSelections.begin();
  481. }
  482. UIElement* ListView::getSelectedItem() const
  483. {
  484. return mContentElement->getChild(getSelection());
  485. }
  486. std::vector<UIElement*> ListView::getSelectedItems() const
  487. {
  488. std::vector<UIElement*> ret;
  489. for (std::set<unsigned>::const_iterator i = mSelections.begin(); i != mSelections.end(); ++i)
  490. {
  491. UIElement* item = getItem(*i);
  492. if (item)
  493. ret.push_back(item);
  494. }
  495. return ret;
  496. }
  497. void ListView::updateSelectionEffect()
  498. {
  499. unsigned numItems = getNumItems();
  500. for (unsigned i = 0; i < numItems; ++i)
  501. {
  502. UIElement* item = getItem(i);
  503. if ((mHighlightMode != HM_NEVER) && (mSelections.find(i) != mSelections.end()))
  504. item->setSelected((mFocus) || (mHighlightMode == HM_ALWAYS));
  505. else
  506. item->setSelected(false);
  507. }
  508. }
  509. void ListView::ensureItemVisibility(unsigned index)
  510. {
  511. UIElement* item = getItem(index);
  512. if (!item)
  513. return;
  514. IntVector2 currentOffset = item->getScreenPosition() - mScrollPanel->getScreenPosition() - mContentElement->getPosition();
  515. IntVector2 newView = getViewPosition();
  516. const IntRect& clipBorder = mScrollPanel->getClipBorder();
  517. IntVector2 windowSize(mScrollPanel->getWidth() - clipBorder.mLeft - clipBorder.mRight, mScrollPanel->getHeight() -
  518. clipBorder.mTop - clipBorder.mBottom);
  519. if (currentOffset.mY < 0)
  520. newView.mY += currentOffset.mY;
  521. if (currentOffset.mY + item->getHeight() > windowSize.mY)
  522. newView.mY += currentOffset.mY + item->getHeight() - windowSize.mY;
  523. setViewPosition(newView);
  524. }
  525. void ListView::handleUIMouseClick(StringHash eventType, VariantMap& eventData)
  526. {
  527. if (eventData[UIMouseClick::P_BUTTON].getInt() != MOUSEB_LEFT)
  528. return;
  529. int qualifiers = eventData[UIMouseClick::P_QUALIFIERS].getInt();
  530. UIElement* element = static_cast<UIElement*>(eventData[UIMouseClick::P_ELEMENT].getPtr());
  531. unsigned numItems = getNumItems();
  532. for (unsigned i = 0; i < numItems; ++i)
  533. {
  534. if (element == getItem(i))
  535. {
  536. // Check doubleclick
  537. bool isDoubleClick = false;
  538. if ((!mMultiselect) || (!qualifiers))
  539. {
  540. if ((mDoubleClickTimer > 0.0f) && (mLastClickedItem == i))
  541. {
  542. isDoubleClick = true;
  543. mDoubleClickTimer = 0.0f;
  544. }
  545. else
  546. {
  547. mDoubleClickTimer = mDoubleClickInterval;
  548. mLastClickedItem = i;
  549. }
  550. setSelection(i);
  551. }
  552. // Check multiselect with shift & ctrl
  553. if (mMultiselect)
  554. {
  555. if (qualifiers & QUAL_SHIFT)
  556. {
  557. if (mSelections.empty())
  558. setSelection(i);
  559. else
  560. {
  561. unsigned first = *mSelections.begin();
  562. unsigned last = *mSelections.rbegin();
  563. std::set<unsigned> newSelections = mSelections;
  564. if ((i == first) || (i == last))
  565. {
  566. for (unsigned j = first; j <= last; ++j)
  567. newSelections.insert(j);
  568. }
  569. else if (i < first)
  570. {
  571. for (unsigned j = i; j <= first; ++j)
  572. newSelections.insert(j);
  573. }
  574. else if (i < last)
  575. {
  576. if ((abs((int)i - (int)first)) <= (abs((int)i - (int)last)))
  577. {
  578. for (unsigned j = first; j <= i; ++j)
  579. newSelections.insert(j);
  580. }
  581. else
  582. {
  583. for (unsigned j = i; j <= last; ++j)
  584. newSelections.insert(j);
  585. }
  586. }
  587. else if (i > last)
  588. {
  589. for (unsigned j = last; j <= i; ++j)
  590. newSelections.insert(j);
  591. }
  592. setSelections(newSelections);
  593. }
  594. }
  595. else if (qualifiers & QUAL_CTRL)
  596. toggleSelection(i);
  597. }
  598. if (isDoubleClick)
  599. {
  600. if (mHierarchyMode)
  601. toggleChildItemsVisible(i);
  602. VariantMap eventData;
  603. eventData[ItemDoubleClicked::P_ELEMENT] = (void*)this;
  604. eventData[ItemDoubleClicked::P_SELECTION] = i;
  605. sendEvent(EVENT_ITEMDOUBLECLICKED, eventData);
  606. }
  607. return;
  608. }
  609. }
  610. }