guiListBoxCtrl.cc 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946
  1. //-----------------------------------------------------------------------------
  2. // Copyright (c) 2013 GarageGames, LLC
  3. //
  4. // Permission is hereby granted, free of charge, to any person obtaining a copy
  5. // of this software and associated documentation files (the "Software"), to
  6. // deal in the Software without restriction, including without limitation the
  7. // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
  8. // sell copies of the Software, and to permit persons to whom the Software is
  9. // furnished to do so, subject to the following conditions:
  10. //
  11. // The above copyright notice and this permission notice shall be included in
  12. // all copies or substantial portions of the Software.
  13. //
  14. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  19. // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
  20. // IN THE SOFTWARE.
  21. //-----------------------------------------------------------------------------
  22. #include "platform/platform.h"
  23. #include "gui/guiListBoxCtrl.h"
  24. #include "gui/guiCanvas.h"
  25. #include <algorithm>
  26. #include "guiListBoxCtrl_ScriptBinding.h"
  27. IMPLEMENT_CONOBJECT(GuiListBoxCtrl);
  28. GuiListBoxCtrl::GuiListBoxCtrl()
  29. {
  30. mItems.clear();
  31. mSelectedItems.clear();
  32. mMultipleSelections = true;
  33. mFitParentWidth = true;
  34. mItemSize = Point2I(10,20);
  35. mLastClickItem = NULL;
  36. mIsContainer = false;
  37. mActive = true;
  38. caller = this;
  39. setField("profile", "GuiListBoxProfile");
  40. }
  41. GuiListBoxCtrl::~GuiListBoxCtrl()
  42. {
  43. clearItems();
  44. }
  45. void GuiListBoxCtrl::initPersistFields()
  46. {
  47. Parent::initPersistFields();
  48. addField( "AllowMultipleSelections", TypeBool, Offset( mMultipleSelections, GuiListBoxCtrl) );
  49. addField( "FitParentWidth", TypeBool, Offset( mFitParentWidth, GuiListBoxCtrl) );
  50. }
  51. bool GuiListBoxCtrl::onWake()
  52. {
  53. if( !Parent::onWake() )
  54. return false;
  55. updateSize();
  56. return true;
  57. }
  58. void GuiListBoxCtrl::addObject(SimObject *obj)
  59. {
  60. AssertWarn(0, "GuiListBoxCtrl::addObject: cannot add children to the GuiListBoxCtrl. It is not a container. Child was not added.");
  61. removeObject(obj);
  62. }
  63. #pragma region ItemAccessors
  64. void GuiListBoxCtrl::clearItems()
  65. {
  66. // Free item list allocated memory
  67. while( mItems.size() )
  68. deleteItem( 0 );
  69. // Free our vector lists
  70. mItems.clear();
  71. mSelectedItems.clear();
  72. }
  73. void GuiListBoxCtrl::clearSelection()
  74. {
  75. if( !mSelectedItems.size() )
  76. return;
  77. VectorPtr<LBItem*>::iterator i = mSelectedItems.begin();
  78. for( ; i != mSelectedItems.end(); i++ )
  79. (*i)->isSelected = false;
  80. mSelectedItems.clear();
  81. if (caller->isMethod("onUnselectAll"))
  82. {
  83. Con::executef(caller, 1, "onUnselectAll");
  84. }
  85. }
  86. void GuiListBoxCtrl::removeSelection( S32 index )
  87. {
  88. // Range Check
  89. if( index >= mItems.size() || index < 0 )
  90. {
  91. Con::warnf("GuiListBoxCtrl::removeSelection - index out of range!" );
  92. return;
  93. }
  94. removeSelection( mItems[index], index );
  95. }
  96. void GuiListBoxCtrl::removeSelection( LBItem *item, S32 index )
  97. {
  98. if( !mSelectedItems.size() )
  99. return;
  100. if( !item )
  101. return;
  102. for( S32 i = 0 ; i < mSelectedItems.size(); i++ )
  103. {
  104. if( mSelectedItems[i] == item )
  105. {
  106. mSelectedItems.erase( &mSelectedItems[i] );
  107. item->isSelected = false;
  108. if (caller->isMethod("onUnselect"))
  109. {
  110. Con::executef(caller, 4, "onUnselect", Con::getIntArg( index ), item->itemText, Con::getIntArg(item->ID));
  111. }
  112. return;
  113. }
  114. }
  115. }
  116. void GuiListBoxCtrl::addSelection( S32 index )
  117. {
  118. // Range Check
  119. if( index >= mItems.size() || index < 0 )
  120. {
  121. Con::warnf("GuiListBoxCtrl::addSelection- index out of range!" );
  122. return;
  123. }
  124. addSelection( mItems[index], index );
  125. }
  126. void GuiListBoxCtrl::addSelection( LBItem *item, S32 index )
  127. {
  128. if( !mMultipleSelections )
  129. {
  130. if( !mSelectedItems.empty() )
  131. {
  132. LBItem* selItem = mSelectedItems.front();
  133. if( selItem != item )
  134. clearSelection();
  135. else
  136. return;
  137. }
  138. }
  139. else
  140. {
  141. if( !mSelectedItems.empty() )
  142. {
  143. for( S32 i = 0; i < mSelectedItems.size(); i++ )
  144. {
  145. if( mSelectedItems[ i ] == item )
  146. return;
  147. }
  148. }
  149. }
  150. item->isSelected = true;
  151. mSelectedItems.push_front( item );
  152. ScrollToIndex(index);
  153. if(caller->isMethod("onSelect"))
  154. Con::executef(caller, 4, "onSelect", Con::getIntArg( index ), item->itemText, Con::getIntArg(item->ID));
  155. }
  156. S32 GuiListBoxCtrl::getItemIndex( LBItem *item )
  157. {
  158. if( mItems.empty() )
  159. return -1;
  160. // Lookup the index of an item in our list, by the pointer to the item
  161. for( S32 i = 0; i < mItems.size(); i++ )
  162. if( mItems[i] == item )
  163. return i;
  164. return -1;
  165. }
  166. S32 GuiListBoxCtrl::getItemCount()
  167. {
  168. return mItems.size();
  169. }
  170. S32 GuiListBoxCtrl::getSelCount()
  171. {
  172. return mSelectedItems.size();
  173. }
  174. S32 GuiListBoxCtrl::getSelectedItem()
  175. {
  176. if( mSelectedItems.empty() || mItems.empty() )
  177. return -1;
  178. for( S32 i = 0 ; i < mItems.size(); i++ )
  179. if( mItems[i]->isSelected )
  180. return i;
  181. return -1;
  182. }
  183. void GuiListBoxCtrl::getSelectedItems( Vector<S32> &Items )
  184. {
  185. // Clear our return vector
  186. Items.clear();
  187. // If there are no selected items, return an empty vector
  188. if( mSelectedItems.empty() )
  189. return;
  190. for( S32 i = 0; i < mItems.size(); i++ )
  191. if( mItems[i]->isSelected )
  192. Items.push_back( i );
  193. }
  194. S32 GuiListBoxCtrl::findItemText( StringTableEntry text, bool caseSensitive )
  195. {
  196. // Check Proper Arguments
  197. if( !text || !text[0] || text == StringTable->EmptyString )
  198. {
  199. Con::warnf("GuiListBoxCtrl::findItemText - No Text Specified!");
  200. return -1;
  201. }
  202. // Check Items Exist.
  203. if( mItems.empty() )
  204. return -1;
  205. // Lookup the index of an item in our list, by the pointer to the item
  206. for( S32 i = 0; i < mItems.size(); i++ )
  207. {
  208. // Case Sensitive Compare?
  209. if( caseSensitive && ( dStrcmp( mItems[i]->itemText, text ) == 0 ) )
  210. return i;
  211. else if (!caseSensitive && ( dStricmp( mItems[i]->itemText, text ) == 0 ))
  212. return i;
  213. }
  214. // Not Found!
  215. return -1;
  216. }
  217. void GuiListBoxCtrl::setSelectionInternal(StringTableEntry text)
  218. {
  219. if(text != StringTable->EmptyString)
  220. {
  221. S32 index = findItemText(text);
  222. if (index != -1)
  223. {
  224. mSelectedItems.clear();
  225. LBItem *item = mItems[index];
  226. item->isSelected = true;
  227. mSelectedItems.push_front(item);
  228. }
  229. }
  230. }
  231. void GuiListBoxCtrl::setCurSel( S32 index )
  232. {
  233. // Range Check
  234. if( index >= mItems.size() )
  235. {
  236. Con::warnf("GuiListBoxCtrl::setCurSel - index out of range!" );
  237. return;
  238. }
  239. // If index -1 is specified, we clear the selection
  240. if( index == -1 )
  241. {
  242. for (auto item : mItems)
  243. {
  244. item->isSelected = false;
  245. }
  246. mSelectedItems.clear();
  247. return;
  248. }
  249. // Add the selection
  250. addSelection( mItems[ index ], index );
  251. }
  252. void GuiListBoxCtrl::setCurSelRange( S32 start, S32 stop )
  253. {
  254. // Verify Selection Range
  255. if( start < 0 )
  256. start = 0;
  257. else if( start > mItems.size() )
  258. start = mItems.size();
  259. if( stop < 0 )
  260. stop = 0;
  261. else if( stop > mItems.size() )
  262. stop = mItems.size();
  263. S32 iterStart = ( start < stop ) ? start : stop;
  264. S32 iterStop = ( start < stop ) ? stop : start;
  265. for( ; iterStart <= iterStop; iterStart++ )
  266. addSelection( mItems[iterStart], iterStart );
  267. }
  268. S32 GuiListBoxCtrl::addItem( StringTableEntry text, void *itemData )
  269. {
  270. // This just calls insert item at the end of the list
  271. return insertItem( mItems.size(), text, itemData );
  272. }
  273. S32 GuiListBoxCtrl::addItemWithColor( StringTableEntry text, ColorF color, void *itemData )
  274. {
  275. // This just calls insert item at the end of the list
  276. return insertItemWithColor( mItems.size(), text, color, itemData );
  277. }
  278. S32 GuiListBoxCtrl::addItemWithID(StringTableEntry text, S32 ID, void *itemData)
  279. {
  280. S32 index = insertItem( mItems.size(), text, itemData);
  281. setItemID(index, ID);
  282. return index;
  283. }
  284. void GuiListBoxCtrl::setItemColor( S32 index, ColorF color )
  285. {
  286. if ((index >= mItems.size()) || index < 0)
  287. {
  288. Con::warnf("GuiListBoxCtrl::setItemColor - invalid index");
  289. return;
  290. }
  291. LBItem* item = mItems[index];
  292. item->hasColor = true;
  293. item->color = color;
  294. }
  295. void GuiListBoxCtrl::clearItemColor(S32 index)
  296. {
  297. if ((index >= mItems.size()) || index < 0)
  298. {
  299. Con::warnf("GuiListBoxCtrl::setItemColor - invalid index");
  300. return;
  301. }
  302. LBItem* item = mItems[index];
  303. item->hasColor = false;
  304. }
  305. void GuiListBoxCtrl::clearAllColors()
  306. {
  307. if (!mSelectedItems.size())
  308. return;
  309. VectorPtr<LBItem*>::iterator i = mSelectedItems.begin();
  310. for (; i != mSelectedItems.end(); i++)
  311. (*i)->hasColor = false;
  312. }
  313. ColorF GuiListBoxCtrl::getItemColor(S32 index)
  314. {
  315. if ((index >= mItems.size()) || index < 0)
  316. {
  317. Con::warnf("GuiListBoxCtrl::getItemColor - invalid index");
  318. return ColorF(0,0,0,0);
  319. }
  320. LBItem* item = mItems[index];
  321. return item->color;
  322. }
  323. bool GuiListBoxCtrl::getItemHasColor(S32 index)
  324. {
  325. if ((index >= mItems.size()) || index < 0)
  326. {
  327. Con::warnf("GuiListBoxCtrl::getItemHasColor - invalid index");
  328. return false;
  329. }
  330. LBItem* item = mItems[index];
  331. return item->hasColor;
  332. }
  333. void GuiListBoxCtrl::setItemID(S32 index, S32 ID)
  334. {
  335. if ((index >= mItems.size()) || index < 0)
  336. {
  337. Con::warnf("GuiListBoxCtrl::setItemID - invalid index");
  338. return;
  339. }
  340. LBItem* item = mItems[index];
  341. item->ID = ID;
  342. }
  343. S32 GuiListBoxCtrl::getItemID(S32 index)
  344. {
  345. if ((index >= mItems.size()) || index < 0)
  346. {
  347. Con::warnf("GuiListBoxCtrl::setItemID - invalid index");
  348. return 0;
  349. }
  350. LBItem* item = mItems[index];
  351. return item->ID;
  352. }
  353. S32 GuiListBoxCtrl::findItemID(S32 ID)
  354. {
  355. // Check Items Exist.
  356. if (mItems.empty())
  357. return -1;
  358. // Lookup the index of an item in our list, by the pointer to the item
  359. for (S32 i = 0; i < mItems.size(); i++)
  360. {
  361. if (mItems[i]->ID == ID)
  362. {
  363. return i;
  364. }
  365. }
  366. // Not Found!
  367. return -1;
  368. }
  369. void GuiListBoxCtrl::setItemActive(S32 index)
  370. {
  371. if ((index >= mItems.size()) || index < 0)
  372. {
  373. Con::warnf("GuiListBoxCtrl::setItemActive - invalid index");
  374. return;
  375. }
  376. LBItem* item = mItems[index];
  377. item->isActive = true;
  378. }
  379. void GuiListBoxCtrl::setItemInactive(S32 index)
  380. {
  381. if ((index >= mItems.size()) || index < 0)
  382. {
  383. Con::warnf("GuiListBoxCtrl::setItemInactive - invalid index");
  384. return;
  385. }
  386. LBItem* item = mItems[index];
  387. item->isActive = false;
  388. }
  389. bool GuiListBoxCtrl::getItemActive(S32 index)
  390. {
  391. if ((index >= mItems.size()) || index < 0)
  392. {
  393. Con::warnf("GuiListBoxCtrl::getItemActive - invalid index");
  394. return true;
  395. }
  396. LBItem* item = mItems[index];
  397. return item->isActive;
  398. }
  399. S32 GuiListBoxCtrl::insertItem( S32 index, StringTableEntry text, void *itemData )
  400. {
  401. // If the index is greater than our list size, insert it at the end
  402. if( index >= mItems.size() )
  403. index = mItems.size();
  404. // Sanity checking
  405. if( !text )
  406. {
  407. Con::warnf("GuiListBoxCtrl::insertItem - cannot add NULL string" );
  408. return -1;
  409. }
  410. LBItem *newItem = createItem();
  411. if( !newItem )
  412. {
  413. Con::warnf("GuiListBoxCtrl::insertItem - error allocating item memory!" );
  414. return -1;
  415. }
  416. // Assign item data
  417. newItem->itemText = StringTable->insert(text, true);
  418. newItem->isSelected = false;
  419. newItem->isActive = true;
  420. newItem->ID = 0;
  421. newItem->itemData = itemData;
  422. newItem->hasColor = false;
  423. // Add to list
  424. mItems.insert(index);
  425. mItems[index] = newItem;
  426. // Resize our list to fit our items
  427. updateSize();
  428. // Return our index in list (last)
  429. return index;
  430. }
  431. GuiListBoxCtrl::LBItem* GuiListBoxCtrl::createItem()
  432. {
  433. LBItem* newItem = new LBItem;
  434. if (!newItem)
  435. {
  436. return nullptr;
  437. }
  438. return newItem;
  439. }
  440. S32 GuiListBoxCtrl::insertItemWithColor( S32 index, StringTableEntry text, ColorF color, void *itemData )
  441. {
  442. if( color == ColorF(-1, -1, -1) )
  443. {
  444. Con::warnf("GuiListBoxCtrl::insertItem - cannot add NULL color" );
  445. return -1;
  446. }
  447. index = insertItem(index, text, itemData);
  448. if(index != -1)
  449. {
  450. setItemColor(index, color);
  451. }
  452. // Return our index in list (last)
  453. return index;
  454. }
  455. void GuiListBoxCtrl::deleteItem( S32 index )
  456. {
  457. // Range Check
  458. if( index >= mItems.size() || index < 0 )
  459. {
  460. Con::warnf("GuiListBoxCtrl::deleteItem - index out of range!" );
  461. return;
  462. }
  463. // Grab our item
  464. LBItem* item = mItems[ index ];
  465. if( !item )
  466. {
  467. Con::warnf("GuiListBoxCtrl::deleteItem - Bad Item Data!" );
  468. return;
  469. }
  470. // Remove it from the selected list.
  471. if( item->isSelected )
  472. {
  473. for( VectorPtr<LBItem*>::iterator i = mSelectedItems.begin(); i != mSelectedItems.end(); i++ )
  474. {
  475. if( item == *i )
  476. {
  477. mSelectedItems.erase_fast( i );
  478. break;
  479. }
  480. }
  481. }
  482. // Remove it from the list
  483. mItems.erase( &mItems[ index ] );
  484. // Free the memory associated with it
  485. delete item;
  486. }
  487. StringTableEntry GuiListBoxCtrl::getItemText( S32 index )
  488. {
  489. // Range Checking
  490. if( index > mItems.size() || index < 0 )
  491. {
  492. Con::warnf( "GuiListBoxCtrl::getItemText - index out of range!" );
  493. return StringTable->EmptyString;
  494. }
  495. return mItems[ index ]->itemText;
  496. }
  497. void GuiListBoxCtrl::setItemText( S32 index, StringTableEntry text )
  498. {
  499. // Sanity Checking
  500. if( !text )
  501. {
  502. Con::warnf("GuiListBoxCtrl::setItemText - Invalid Text Specified!" );
  503. return;
  504. }
  505. // Range Checking
  506. if( index > mItems.size() || index < 0 )
  507. {
  508. Con::warnf( "GuiListBoxCtrl::getItemText - index out of range!" );
  509. return;
  510. }
  511. mItems[ index ]->itemText = StringTable->insert( text );
  512. }
  513. #pragma endregion
  514. #pragma region Sizing
  515. void GuiListBoxCtrl::updateSize()
  516. {
  517. if( !mProfile || !mProfile->getFont(mFontSizeAdjust))
  518. return;
  519. GFont *font = mProfile->getFont(mFontSizeAdjust);
  520. Point2I contentSize = Point2I(10, font->getHeight() + 2);
  521. if (!mFitParentWidth)
  522. {
  523. // Find the maximum width cell:
  524. S32 maxWidth = 1;
  525. for ( U32 i = 0; i < (U32)mItems.size(); i++ )
  526. {
  527. S32 width = font->getStrWidth( mItems[i]->itemText );
  528. if( width > maxWidth )
  529. maxWidth = width;
  530. }
  531. contentSize.x = maxWidth + 6;
  532. }
  533. mItemSize = this->getOuterExtent(contentSize, NormalState, mProfile);
  534. Point2I newExtent = Point2I(mItemSize.x, mItemSize.y * mItems.size());
  535. //Don't update the extent.x if we are matching our parent's size. We will handle it during rendering.
  536. if (mFitParentWidth)
  537. {
  538. newExtent.x = mBounds.extent.x;
  539. }
  540. resize( mBounds.point, newExtent );
  541. }
  542. void GuiListBoxCtrl::parentResized(const Point2I &oldParentExtent, const Point2I &newParentExtent)
  543. {
  544. Parent::parentResized( oldParentExtent, newParentExtent );
  545. updateSize();
  546. }
  547. #pragma endregion
  548. #pragma region Rendering
  549. void GuiListBoxCtrl::onRender( Point2I offset, const RectI &updateRect )
  550. {
  551. RectI clip = dglGetClipRect();
  552. if (mFitParentWidth && ( mBounds.extent.x != clip.extent.x || mItemSize.x != clip.extent.x))
  553. {
  554. mBounds.extent.x = clip.extent.x;
  555. mItemSize.x = clip.extent.x;
  556. }
  557. for ( S32 i = 0; i < mItems.size(); i++)
  558. {
  559. // Only render visible items
  560. if ((i + 1) * mItemSize.y + offset.y < updateRect.point.y)
  561. continue;
  562. // Break out once we're no longer in visible item range
  563. if( i * mItemSize.y + offset.y >= updateRect.point.y + updateRect.extent.y)
  564. break;
  565. RectI itemRect = RectI( offset.x, offset.y + ( i * mItemSize.y ), mItemSize.x, mItemSize.y );
  566. // Render our item
  567. onRenderItem( itemRect, mItems[i] );
  568. }
  569. }
  570. void GuiListBoxCtrl::onRenderItem( RectI &itemRect, LBItem *item )
  571. {
  572. Point2I cursorPt = Point2I(0,0);
  573. GuiCanvas *root = getRoot();
  574. if (root)
  575. {
  576. cursorPt = root->getCursorPos();
  577. }
  578. GuiControlState currentState = GuiControlState::NormalState;
  579. if (!mActive || !item->isActive)
  580. currentState = GuiControlState::DisabledState;
  581. else if (item->isSelected)
  582. currentState = GuiControlState::SelectedState;
  583. else if (itemRect.pointInRect(cursorPt))
  584. currentState = GuiControlState::HighlightState;
  585. RectI ctrlRect = applyMargins(itemRect.point, itemRect.extent, currentState, mProfile);
  586. if (!ctrlRect.isValidRect())
  587. {
  588. return;
  589. }
  590. renderUniversalRect(ctrlRect, mProfile, currentState);
  591. //Render Text
  592. dglSetBitmapModulation(getFontColor(mProfile, currentState));
  593. RectI fillRect = applyBorders(ctrlRect.point, ctrlRect.extent, currentState, mProfile);
  594. RectI contentRect = applyPadding(fillRect.point, fillRect.extent, currentState, mProfile);
  595. // Render color bullet if needed
  596. if (item->hasColor)
  597. {
  598. RectI drawArea = RectI(contentRect.point.x, contentRect.point.y, contentRect.extent.y, contentRect.extent.y);
  599. ColorI color = item->color;
  600. renderColorBullet(drawArea, color, 5);
  601. contentRect.point.x += contentRect.extent.y;
  602. contentRect.extent.x -= contentRect.extent.y;
  603. }
  604. renderText(contentRect.point, contentRect.extent, item->itemText, mProfile);
  605. }
  606. void GuiListBoxCtrl::ScrollToIndex(const S32 targetIndex)
  607. {
  608. GuiScrollCtrl* parent = dynamic_cast<GuiScrollCtrl *>(getParent());
  609. if (parent)
  610. parent->scrollRectVisible(RectI(0, mItemSize.y * targetIndex, mItemSize.x, mItemSize.y));
  611. }
  612. #pragma endregion
  613. #pragma region InputEvents
  614. void GuiListBoxCtrl::onTouchDragged(const GuiEvent &event)
  615. {
  616. if (!mActive || !mVisible)
  617. {
  618. return;
  619. }
  620. S32 hitIndex = getHitIndex(event);
  621. if (hitIndex >= mItems.size() || hitIndex == -1)
  622. return;
  623. LBItem *hitItem = mItems[hitIndex];
  624. if (hitItem == NULL || !hitItem->isActive)
  625. return;
  626. if(caller->isMethod("onTouchDragged"))
  627. {
  628. Con::executef(caller, 4, "onTouchDragged", Con::getIntArg(hitIndex), hitItem->itemText, Con::getIntArg(hitItem->ID));
  629. }
  630. else
  631. {
  632. GuiControl* parent = getParent();
  633. if (parent)
  634. parent->onTouchDragged(event);
  635. }
  636. }
  637. void GuiListBoxCtrl::onTouchDown( const GuiEvent &event )
  638. {
  639. if (!mActive || !mVisible)
  640. {
  641. return;
  642. }
  643. setFirstResponder();
  644. S32 hitIndex = getHitIndex(event);
  645. if ( hitIndex >= mItems.size() || hitIndex == -1 )
  646. return;
  647. LBItem *hitItem = mItems[ hitIndex ];
  648. if ( hitItem == NULL || !hitItem->isActive)
  649. return;
  650. handleItemClick(hitItem, hitIndex, event);
  651. }
  652. S32 GuiListBoxCtrl::getHitIndex(const GuiEvent& event)
  653. {
  654. Point2I localPoint = globalToLocalCoord(event.mousePoint);
  655. return (localPoint.y < 0) ? -1 : (S32)mFloor((F32)localPoint.y / (F32)mItemSize.y);
  656. }
  657. void GuiListBoxCtrl::handleItemClick(LBItem* hitItem, S32 hitIndex, const GuiEvent& event)
  658. {
  659. if( !mMultipleSelections )
  660. {
  661. handleItemClick_SingleSelection(hitItem, hitIndex, event);
  662. }
  663. else
  664. {
  665. if (!handleItemClick_MultiSelection(hitItem, hitIndex, event))
  666. {
  667. return;
  668. }
  669. }
  670. handleItemClick_ClickCallbacks(hitItem, hitIndex, event);
  671. mLastClickItem = hitItem;
  672. }
  673. void GuiListBoxCtrl::handleItemClick_SingleSelection(LBItem* hitItem, S32 hitIndex, const GuiEvent& event)
  674. {
  675. // No current selection? Just select the cell and move on
  676. S32 selItem = getSelectedItem();
  677. if (selItem != hitIndex && selItem != -1)
  678. clearSelection();
  679. // Set the current selection
  680. setCurSel(hitIndex);
  681. }
  682. bool GuiListBoxCtrl::handleItemClick_MultiSelection(LBItem* hitItem, S32 hitIndex, const GuiEvent& event)
  683. {
  684. // Deal with multiple selections
  685. if (event.modifier & SI_CTRL)
  686. {
  687. // Ctrl-Click toggles selection
  688. if (hitItem->isSelected)
  689. {
  690. removeSelection(hitItem, hitIndex);
  691. // We return here when we deselect an item because we don't store last clicked when we deselect
  692. return false;
  693. }
  694. else
  695. addSelection(hitItem, hitIndex);
  696. }
  697. else if (event.modifier & SI_SHIFT)
  698. {
  699. if (!mLastClickItem)
  700. addSelection(hitItem, hitIndex);
  701. else
  702. setCurSelRange(getItemIndex(mLastClickItem), hitIndex);
  703. }
  704. else
  705. {
  706. if (getSelCount() != 0)
  707. {
  708. S32 selItem = getSelectedItem();
  709. if (selItem != -1 && mItems[selItem] != hitItem)
  710. clearSelection();
  711. }
  712. addSelection(hitItem, hitIndex);
  713. }
  714. return true;
  715. }
  716. void GuiListBoxCtrl::handleItemClick_ClickCallbacks(LBItem* hitItem, S32 hitIndex, const GuiEvent& event)
  717. {
  718. if (hitItem == mLastClickItem && event.mouseClickCount == 2)
  719. {
  720. if (caller->isMethod("onDoubleClick"))
  721. Con::executef(caller, 4, "onDoubleClick", Con::getIntArg(hitIndex), hitItem->itemText, Con::getIntArg(hitItem->ID));
  722. }
  723. else if (caller->isMethod("onClick"))
  724. {
  725. Con::executef(caller, 4, "onClick", Con::getIntArg(hitIndex), hitItem->itemText, Con::getIntArg(hitItem->ID));
  726. }
  727. }
  728. bool GuiListBoxCtrl::onKeyDown(const GuiEvent &event)
  729. {
  730. //if this control is a dead end, make sure the event stops here
  731. if (!mVisible || !mActive || !mAwake || mItems.size() == 0)
  732. return true;
  733. S32 index = getSelectedItem();
  734. switch (event.keyCode)
  735. {
  736. case KEY_RETURN:
  737. if (mAltConsoleCommand[0])
  738. Con::evaluate(mAltConsoleCommand, false);
  739. return true;
  740. case KEY_LEFT:
  741. case KEY_UP:
  742. if (index == -1)
  743. {
  744. //Select the bottom item
  745. addSelection(mItems.size() - 1);
  746. return true;
  747. }
  748. else if(index != 0)
  749. {
  750. addSelection(index - 1);
  751. return true;
  752. }
  753. break;
  754. case KEY_DOWN:
  755. case KEY_RIGHT:
  756. if (index == -1)
  757. {
  758. //Select the top item
  759. addSelection(0);
  760. return true;
  761. }
  762. else if (index != (mItems.size() - 1))
  763. {
  764. addSelection(index + 1);
  765. return true;
  766. }
  767. break;
  768. case KEY_HOME:
  769. addSelection(0);
  770. return true;
  771. case KEY_END:
  772. addSelection(mItems.size() - 1);
  773. return true;
  774. case KEY_DELETE:
  775. if (index != -1 && isMethod("onDeleteKey"))
  776. Con::executef(caller, 4, "onDeleteKey", Con::getIntArg(index), getItemText(index), Con::getIntArg(getItemID(index)));
  777. return true;
  778. default:
  779. return Parent::onKeyDown(event);
  780. };
  781. return false;
  782. }
  783. #pragma endregion
  784. #pragma region sorting
  785. bool GuiListBoxCtrl::LBItem::sIncreasing = true;
  786. void GuiListBoxCtrl::sortByText(bool increasing)
  787. {
  788. if (mItems.size() < 2)
  789. return;
  790. LBItem::sIncreasing = increasing;
  791. std::sort(mItems.begin(), mItems.end(), LBItem::compByText);
  792. }
  793. void GuiListBoxCtrl::sortByID(bool increasing)
  794. {
  795. if (mItems.size() < 2)
  796. return;
  797. LBItem::sIncreasing = increasing;
  798. std::sort(mItems.begin(), mItems.end(), LBItem::compByID);
  799. }
  800. #pragma endregion