juce_ListBox.cpp 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969
  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2013 - Raw Material Software Ltd.
  5. Permission is granted to use this software under the terms of either:
  6. a) the GPL v2 (or any later version)
  7. b) the Affero GPL v3
  8. Details of these licenses can be found at: www.gnu.org/licenses
  9. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  10. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  11. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  12. ------------------------------------------------------------------------------
  13. To release a closed-source product which uses JUCE, commercial licenses are
  14. available: visit www.juce.com for more information.
  15. ==============================================================================
  16. */
  17. class ListBox::RowComponent : public Component,
  18. public TooltipClient
  19. {
  20. public:
  21. RowComponent (ListBox& lb)
  22. : owner (lb), row (-1),
  23. selected (false), isDragging (false), selectRowOnMouseUp (false)
  24. {
  25. }
  26. void paint (Graphics& g) override
  27. {
  28. if (ListBoxModel* m = owner.getModel())
  29. m->paintListBoxItem (row, g, getWidth(), getHeight(), selected);
  30. }
  31. void update (const int newRow, const bool nowSelected)
  32. {
  33. if (row != newRow || selected != nowSelected)
  34. {
  35. repaint();
  36. row = newRow;
  37. selected = nowSelected;
  38. }
  39. if (ListBoxModel* m = owner.getModel())
  40. {
  41. setMouseCursor (m->getMouseCursorForRow (row));
  42. customComponent = m->refreshComponentForRow (newRow, nowSelected, customComponent.release());
  43. if (customComponent != nullptr)
  44. {
  45. addAndMakeVisible (customComponent);
  46. customComponent->setBounds (getLocalBounds());
  47. }
  48. }
  49. }
  50. void mouseDown (const MouseEvent& e) override
  51. {
  52. isDragging = false;
  53. selectRowOnMouseUp = false;
  54. if (isEnabled())
  55. {
  56. if (! selected)
  57. {
  58. owner.selectRowsBasedOnModifierKeys (row, e.mods, false);
  59. if (ListBoxModel* m = owner.getModel())
  60. m->listBoxItemClicked (row, e);
  61. }
  62. else
  63. {
  64. selectRowOnMouseUp = true;
  65. }
  66. }
  67. }
  68. void mouseUp (const MouseEvent& e) override
  69. {
  70. if (isEnabled() && selectRowOnMouseUp && ! isDragging)
  71. {
  72. owner.selectRowsBasedOnModifierKeys (row, e.mods, true);
  73. if (ListBoxModel* m = owner.getModel())
  74. m->listBoxItemClicked (row, e);
  75. }
  76. }
  77. void mouseDoubleClick (const MouseEvent& e) override
  78. {
  79. if (ListBoxModel* m = owner.getModel())
  80. if (isEnabled())
  81. m->listBoxItemDoubleClicked (row, e);
  82. }
  83. void mouseDrag (const MouseEvent& e) override
  84. {
  85. if (ListBoxModel* m = owner.getModel())
  86. {
  87. if (isEnabled() && ! (e.mouseWasClicked() || isDragging))
  88. {
  89. const SparseSet<int> selectedRows (owner.getSelectedRows());
  90. if (selectedRows.size() > 0)
  91. {
  92. const var dragDescription (m->getDragSourceDescription (selectedRows));
  93. if (! (dragDescription.isVoid() || (dragDescription.isString() && dragDescription.toString().isEmpty())))
  94. {
  95. isDragging = true;
  96. owner.startDragAndDrop (e, dragDescription, true);
  97. }
  98. }
  99. }
  100. }
  101. }
  102. void resized() override
  103. {
  104. if (customComponent != nullptr)
  105. customComponent->setBounds (getLocalBounds());
  106. }
  107. String getTooltip() override
  108. {
  109. if (ListBoxModel* m = owner.getModel())
  110. return m->getTooltipForRow (row);
  111. return String::empty;
  112. }
  113. ScopedPointer<Component> customComponent;
  114. private:
  115. ListBox& owner;
  116. int row;
  117. bool selected, isDragging, selectRowOnMouseUp;
  118. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (RowComponent)
  119. };
  120. //==============================================================================
  121. class ListBox::ListViewport : public Viewport
  122. {
  123. public:
  124. ListViewport (ListBox& lb)
  125. : owner (lb)
  126. {
  127. setWantsKeyboardFocus (false);
  128. Component* const content = new Component();
  129. setViewedComponent (content);
  130. content->setWantsKeyboardFocus (false);
  131. }
  132. RowComponent* getComponentForRow (const int row) const noexcept
  133. {
  134. return rows [row % jmax (1, rows.size())];
  135. }
  136. RowComponent* getComponentForRowIfOnscreen (const int row) const noexcept
  137. {
  138. return (row >= firstIndex && row < firstIndex + rows.size())
  139. ? getComponentForRow (row) : nullptr;
  140. }
  141. int getRowNumberOfComponent (Component* const rowComponent) const noexcept
  142. {
  143. const int index = getViewedComponent()->getIndexOfChildComponent (rowComponent);
  144. const int num = rows.size();
  145. for (int i = num; --i >= 0;)
  146. if (((firstIndex + i) % jmax (1, num)) == index)
  147. return firstIndex + i;
  148. return -1;
  149. }
  150. void visibleAreaChanged (const Rectangle<int>&) override
  151. {
  152. updateVisibleArea (true);
  153. if (ListBoxModel* m = owner.getModel())
  154. m->listWasScrolled();
  155. }
  156. void updateVisibleArea (const bool makeSureItUpdatesContent)
  157. {
  158. hasUpdated = false;
  159. Component& content = *getViewedComponent();
  160. const int newX = content.getX();
  161. int newY = content.getY();
  162. const int newW = jmax (owner.minimumRowWidth, getMaximumVisibleWidth());
  163. const int newH = owner.totalItems * owner.getRowHeight();
  164. if (newY + newH < getMaximumVisibleHeight() && newH > getMaximumVisibleHeight())
  165. newY = getMaximumVisibleHeight() - newH;
  166. content.setBounds (newX, newY, newW, newH);
  167. if (makeSureItUpdatesContent && ! hasUpdated)
  168. updateContents();
  169. }
  170. void updateContents()
  171. {
  172. hasUpdated = true;
  173. const int rowH = owner.getRowHeight();
  174. Component& content = *getViewedComponent();
  175. if (rowH > 0)
  176. {
  177. const int y = getViewPositionY();
  178. const int w = content.getWidth();
  179. const int numNeeded = 2 + getMaximumVisibleHeight() / rowH;
  180. rows.removeRange (numNeeded, rows.size());
  181. while (numNeeded > rows.size())
  182. {
  183. RowComponent* newRow = new RowComponent (owner);
  184. rows.add (newRow);
  185. content.addAndMakeVisible (newRow);
  186. }
  187. firstIndex = y / rowH;
  188. firstWholeIndex = (y + rowH - 1) / rowH;
  189. lastWholeIndex = (y + getMaximumVisibleHeight() - 1) / rowH;
  190. for (int i = 0; i < numNeeded; ++i)
  191. {
  192. const int row = i + firstIndex;
  193. if (RowComponent* const rowComp = getComponentForRow (row))
  194. {
  195. rowComp->setBounds (0, row * rowH, w, rowH);
  196. rowComp->update (row, owner.isRowSelected (row));
  197. }
  198. }
  199. }
  200. if (owner.headerComponent != nullptr)
  201. owner.headerComponent->setBounds (owner.outlineThickness + content.getX(),
  202. owner.outlineThickness,
  203. jmax (owner.getWidth() - owner.outlineThickness * 2,
  204. content.getWidth()),
  205. owner.headerComponent->getHeight());
  206. }
  207. void selectRow (const int row, const int rowH, const bool dontScroll,
  208. const int lastSelectedRow, const int totalRows, const bool isMouseClick)
  209. {
  210. hasUpdated = false;
  211. if (row < firstWholeIndex && ! dontScroll)
  212. {
  213. setViewPosition (getViewPositionX(), row * rowH);
  214. }
  215. else if (row >= lastWholeIndex && ! dontScroll)
  216. {
  217. const int rowsOnScreen = lastWholeIndex - firstWholeIndex;
  218. if (row >= lastSelectedRow + rowsOnScreen
  219. && rowsOnScreen < totalRows - 1
  220. && ! isMouseClick)
  221. {
  222. setViewPosition (getViewPositionX(),
  223. jlimit (0, jmax (0, totalRows - rowsOnScreen), row) * rowH);
  224. }
  225. else
  226. {
  227. setViewPosition (getViewPositionX(),
  228. jmax (0, (row + 1) * rowH - getMaximumVisibleHeight()));
  229. }
  230. }
  231. if (! hasUpdated)
  232. updateContents();
  233. }
  234. void scrollToEnsureRowIsOnscreen (const int row, const int rowH)
  235. {
  236. if (row < firstWholeIndex)
  237. {
  238. setViewPosition (getViewPositionX(), row * rowH);
  239. }
  240. else if (row >= lastWholeIndex)
  241. {
  242. setViewPosition (getViewPositionX(),
  243. jmax (0, (row + 1) * rowH - getMaximumVisibleHeight()));
  244. }
  245. }
  246. void paint (Graphics& g) override
  247. {
  248. if (isOpaque())
  249. g.fillAll (owner.findColour (ListBox::backgroundColourId));
  250. }
  251. bool keyPressed (const KeyPress& key) override
  252. {
  253. if (Viewport::respondsToKey (key))
  254. {
  255. const int allowableMods = owner.multipleSelection ? ModifierKeys::shiftModifier : 0;
  256. if ((key.getModifiers().getRawFlags() & ~allowableMods) == 0)
  257. {
  258. // we want to avoid these keypresses going to the viewport, and instead allow
  259. // them to pass up to our listbox..
  260. return false;
  261. }
  262. }
  263. return Viewport::keyPressed (key);
  264. }
  265. private:
  266. ListBox& owner;
  267. OwnedArray<RowComponent> rows;
  268. int firstIndex, firstWholeIndex, lastWholeIndex;
  269. bool hasUpdated;
  270. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ListViewport)
  271. };
  272. //==============================================================================
  273. class ListBoxMouseMoveSelector : public MouseListener
  274. {
  275. public:
  276. ListBoxMouseMoveSelector (ListBox& lb) : owner (lb)
  277. {
  278. owner.addMouseListener (this, true);
  279. }
  280. void mouseMove (const MouseEvent& e) override
  281. {
  282. const MouseEvent e2 (e.getEventRelativeTo (&owner));
  283. owner.selectRow (owner.getRowContainingPosition (e2.x, e2.y), true);
  284. }
  285. void mouseExit (const MouseEvent& e) override
  286. {
  287. mouseMove (e);
  288. }
  289. private:
  290. ListBox& owner;
  291. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ListBoxMouseMoveSelector)
  292. };
  293. //==============================================================================
  294. ListBox::ListBox (const String& name, ListBoxModel* const m)
  295. : Component (name),
  296. model (m),
  297. totalItems (0),
  298. rowHeight (22),
  299. minimumRowWidth (0),
  300. outlineThickness (0),
  301. lastRowSelected (-1),
  302. multipleSelection (false),
  303. alwaysFlipSelection (false),
  304. hasDoneInitialUpdate (false)
  305. {
  306. addAndMakeVisible (viewport = new ListViewport (*this));
  307. ListBox::setWantsKeyboardFocus (true);
  308. ListBox::colourChanged();
  309. }
  310. ListBox::~ListBox()
  311. {
  312. headerComponent = nullptr;
  313. viewport = nullptr;
  314. }
  315. void ListBox::setModel (ListBoxModel* const newModel)
  316. {
  317. if (model != newModel)
  318. {
  319. model = newModel;
  320. repaint();
  321. updateContent();
  322. }
  323. }
  324. void ListBox::setMultipleSelectionEnabled (bool b) noexcept
  325. {
  326. multipleSelection = b;
  327. }
  328. void ListBox::setClickingTogglesRowSelection (bool b) noexcept
  329. {
  330. alwaysFlipSelection = b;
  331. }
  332. void ListBox::setMouseMoveSelectsRows (bool b)
  333. {
  334. if (b)
  335. {
  336. if (mouseMoveSelector == nullptr)
  337. mouseMoveSelector = new ListBoxMouseMoveSelector (*this);
  338. }
  339. else
  340. {
  341. mouseMoveSelector = nullptr;
  342. }
  343. }
  344. //==============================================================================
  345. void ListBox::paint (Graphics& g)
  346. {
  347. if (! hasDoneInitialUpdate)
  348. updateContent();
  349. g.fillAll (findColour (backgroundColourId));
  350. }
  351. void ListBox::paintOverChildren (Graphics& g)
  352. {
  353. if (outlineThickness > 0)
  354. {
  355. g.setColour (findColour (outlineColourId));
  356. g.drawRect (getLocalBounds(), outlineThickness);
  357. }
  358. }
  359. void ListBox::resized()
  360. {
  361. viewport->setBoundsInset (BorderSize<int> (outlineThickness + (headerComponent != nullptr ? headerComponent->getHeight() : 0),
  362. outlineThickness, outlineThickness, outlineThickness));
  363. viewport->setSingleStepSizes (20, getRowHeight());
  364. viewport->updateVisibleArea (false);
  365. }
  366. void ListBox::visibilityChanged()
  367. {
  368. viewport->updateVisibleArea (true);
  369. }
  370. Viewport* ListBox::getViewport() const noexcept
  371. {
  372. return viewport;
  373. }
  374. //==============================================================================
  375. void ListBox::updateContent()
  376. {
  377. hasDoneInitialUpdate = true;
  378. totalItems = (model != nullptr) ? model->getNumRows() : 0;
  379. bool selectionChanged = false;
  380. if (selected.size() > 0 && selected [selected.size() - 1] >= totalItems)
  381. {
  382. selected.removeRange (Range<int> (totalItems, std::numeric_limits<int>::max()));
  383. lastRowSelected = getSelectedRow (0);
  384. selectionChanged = true;
  385. }
  386. viewport->updateVisibleArea (isVisible());
  387. viewport->resized();
  388. if (selectionChanged && model != nullptr)
  389. model->selectedRowsChanged (lastRowSelected);
  390. }
  391. //==============================================================================
  392. void ListBox::selectRow (int row, bool dontScroll, bool deselectOthersFirst)
  393. {
  394. selectRowInternal (row, dontScroll, deselectOthersFirst, false);
  395. }
  396. void ListBox::selectRowInternal (const int row,
  397. bool dontScroll,
  398. bool deselectOthersFirst,
  399. bool isMouseClick)
  400. {
  401. if (! multipleSelection)
  402. deselectOthersFirst = true;
  403. if ((! isRowSelected (row))
  404. || (deselectOthersFirst && getNumSelectedRows() > 1))
  405. {
  406. if (isPositiveAndBelow (row, totalItems))
  407. {
  408. if (deselectOthersFirst)
  409. selected.clear();
  410. selected.addRange (Range<int> (row, row + 1));
  411. if (getHeight() == 0 || getWidth() == 0)
  412. dontScroll = true;
  413. viewport->selectRow (row, getRowHeight(), dontScroll,
  414. lastRowSelected, totalItems, isMouseClick);
  415. lastRowSelected = row;
  416. model->selectedRowsChanged (row);
  417. }
  418. else
  419. {
  420. if (deselectOthersFirst)
  421. deselectAllRows();
  422. }
  423. }
  424. }
  425. void ListBox::deselectRow (const int row)
  426. {
  427. if (selected.contains (row))
  428. {
  429. selected.removeRange (Range<int> (row, row + 1));
  430. if (row == lastRowSelected)
  431. lastRowSelected = getSelectedRow (0);
  432. viewport->updateContents();
  433. model->selectedRowsChanged (lastRowSelected);
  434. }
  435. }
  436. void ListBox::setSelectedRows (const SparseSet<int>& setOfRowsToBeSelected,
  437. const NotificationType sendNotificationEventToModel)
  438. {
  439. selected = setOfRowsToBeSelected;
  440. selected.removeRange (Range<int> (totalItems, std::numeric_limits<int>::max()));
  441. if (! isRowSelected (lastRowSelected))
  442. lastRowSelected = getSelectedRow (0);
  443. viewport->updateContents();
  444. if (model != nullptr && sendNotificationEventToModel == sendNotification)
  445. model->selectedRowsChanged (lastRowSelected);
  446. }
  447. SparseSet<int> ListBox::getSelectedRows() const
  448. {
  449. return selected;
  450. }
  451. void ListBox::selectRangeOfRows (int firstRow, int lastRow)
  452. {
  453. if (multipleSelection && (firstRow != lastRow))
  454. {
  455. const int numRows = totalItems - 1;
  456. firstRow = jlimit (0, jmax (0, numRows), firstRow);
  457. lastRow = jlimit (0, jmax (0, numRows), lastRow);
  458. selected.addRange (Range<int> (jmin (firstRow, lastRow),
  459. jmax (firstRow, lastRow) + 1));
  460. selected.removeRange (Range<int> (lastRow, lastRow + 1));
  461. }
  462. selectRowInternal (lastRow, false, false, true);
  463. }
  464. void ListBox::flipRowSelection (const int row)
  465. {
  466. if (isRowSelected (row))
  467. deselectRow (row);
  468. else
  469. selectRowInternal (row, false, false, true);
  470. }
  471. void ListBox::deselectAllRows()
  472. {
  473. if (! selected.isEmpty())
  474. {
  475. selected.clear();
  476. lastRowSelected = -1;
  477. viewport->updateContents();
  478. if (model != nullptr)
  479. model->selectedRowsChanged (lastRowSelected);
  480. }
  481. }
  482. void ListBox::selectRowsBasedOnModifierKeys (const int row,
  483. ModifierKeys mods,
  484. const bool isMouseUpEvent)
  485. {
  486. if (multipleSelection && (mods.isCommandDown() || alwaysFlipSelection))
  487. {
  488. flipRowSelection (row);
  489. }
  490. else if (multipleSelection && mods.isShiftDown() && lastRowSelected >= 0)
  491. {
  492. selectRangeOfRows (lastRowSelected, row);
  493. }
  494. else if ((! mods.isPopupMenu()) || ! isRowSelected (row))
  495. {
  496. selectRowInternal (row, false, ! (multipleSelection && (! isMouseUpEvent) && isRowSelected (row)), true);
  497. }
  498. }
  499. int ListBox::getNumSelectedRows() const
  500. {
  501. return selected.size();
  502. }
  503. int ListBox::getSelectedRow (const int index) const
  504. {
  505. return (isPositiveAndBelow (index, selected.size()))
  506. ? selected [index] : -1;
  507. }
  508. bool ListBox::isRowSelected (const int row) const
  509. {
  510. return selected.contains (row);
  511. }
  512. int ListBox::getLastRowSelected() const
  513. {
  514. return isRowSelected (lastRowSelected) ? lastRowSelected : -1;
  515. }
  516. //==============================================================================
  517. int ListBox::getRowContainingPosition (const int x, const int y) const noexcept
  518. {
  519. if (isPositiveAndBelow (x, getWidth()))
  520. {
  521. const int row = (viewport->getViewPositionY() + y - viewport->getY()) / rowHeight;
  522. if (isPositiveAndBelow (row, totalItems))
  523. return row;
  524. }
  525. return -1;
  526. }
  527. int ListBox::getInsertionIndexForPosition (const int x, const int y) const noexcept
  528. {
  529. if (isPositiveAndBelow (x, getWidth()))
  530. {
  531. const int row = (viewport->getViewPositionY() + y + rowHeight / 2 - viewport->getY()) / rowHeight;
  532. return jlimit (0, totalItems, row);
  533. }
  534. return -1;
  535. }
  536. Component* ListBox::getComponentForRowNumber (const int row) const noexcept
  537. {
  538. if (RowComponent* const listRowComp = viewport->getComponentForRowIfOnscreen (row))
  539. return static_cast<Component*> (listRowComp->customComponent);
  540. return nullptr;
  541. }
  542. int ListBox::getRowNumberOfComponent (Component* const rowComponent) const noexcept
  543. {
  544. return viewport->getRowNumberOfComponent (rowComponent);
  545. }
  546. Rectangle<int> ListBox::getRowPosition (const int rowNumber,
  547. const bool relativeToComponentTopLeft) const noexcept
  548. {
  549. int y = viewport->getY() + rowHeight * rowNumber;
  550. if (relativeToComponentTopLeft)
  551. y -= viewport->getViewPositionY();
  552. return Rectangle<int> (viewport->getX(), y,
  553. viewport->getViewedComponent()->getWidth(), rowHeight);
  554. }
  555. void ListBox::setVerticalPosition (const double proportion)
  556. {
  557. const int offscreen = viewport->getViewedComponent()->getHeight() - viewport->getHeight();
  558. viewport->setViewPosition (viewport->getViewPositionX(),
  559. jmax (0, roundToInt (proportion * offscreen)));
  560. }
  561. double ListBox::getVerticalPosition() const
  562. {
  563. const int offscreen = viewport->getViewedComponent()->getHeight() - viewport->getHeight();
  564. return (offscreen > 0) ? viewport->getViewPositionY() / (double) offscreen
  565. : 0;
  566. }
  567. int ListBox::getVisibleRowWidth() const noexcept
  568. {
  569. return viewport->getViewWidth();
  570. }
  571. void ListBox::scrollToEnsureRowIsOnscreen (const int row)
  572. {
  573. viewport->scrollToEnsureRowIsOnscreen (row, getRowHeight());
  574. }
  575. //==============================================================================
  576. bool ListBox::keyPressed (const KeyPress& key)
  577. {
  578. const int numVisibleRows = viewport->getHeight() / getRowHeight();
  579. const bool multiple = multipleSelection
  580. && lastRowSelected >= 0
  581. && key.getModifiers().isShiftDown();
  582. if (key.isKeyCode (KeyPress::upKey))
  583. {
  584. if (multiple)
  585. selectRangeOfRows (lastRowSelected, lastRowSelected - 1);
  586. else
  587. selectRow (jmax (0, lastRowSelected - 1));
  588. }
  589. else if (key.isKeyCode (KeyPress::downKey))
  590. {
  591. if (multiple)
  592. selectRangeOfRows (lastRowSelected, lastRowSelected + 1);
  593. else
  594. selectRow (jmin (totalItems - 1, jmax (0, lastRowSelected) + 1));
  595. }
  596. else if (key.isKeyCode (KeyPress::pageUpKey))
  597. {
  598. if (multiple)
  599. selectRangeOfRows (lastRowSelected, lastRowSelected - numVisibleRows);
  600. else
  601. selectRow (jmax (0, jmax (0, lastRowSelected) - numVisibleRows));
  602. }
  603. else if (key.isKeyCode (KeyPress::pageDownKey))
  604. {
  605. if (multiple)
  606. selectRangeOfRows (lastRowSelected, lastRowSelected + numVisibleRows);
  607. else
  608. selectRow (jmin (totalItems - 1, jmax (0, lastRowSelected) + numVisibleRows));
  609. }
  610. else if (key.isKeyCode (KeyPress::homeKey))
  611. {
  612. if (multiple)
  613. selectRangeOfRows (lastRowSelected, 0);
  614. else
  615. selectRow (0);
  616. }
  617. else if (key.isKeyCode (KeyPress::endKey))
  618. {
  619. if (multiple)
  620. selectRangeOfRows (lastRowSelected, totalItems - 1);
  621. else
  622. selectRow (totalItems - 1);
  623. }
  624. else if (key.isKeyCode (KeyPress::returnKey) && isRowSelected (lastRowSelected))
  625. {
  626. if (model != nullptr)
  627. model->returnKeyPressed (lastRowSelected);
  628. }
  629. else if ((key.isKeyCode (KeyPress::deleteKey) || key.isKeyCode (KeyPress::backspaceKey))
  630. && isRowSelected (lastRowSelected))
  631. {
  632. if (model != nullptr)
  633. model->deleteKeyPressed (lastRowSelected);
  634. }
  635. else if (multipleSelection && key == KeyPress ('a', ModifierKeys::commandModifier, 0))
  636. {
  637. selectRangeOfRows (0, std::numeric_limits<int>::max());
  638. }
  639. else
  640. {
  641. return false;
  642. }
  643. return true;
  644. }
  645. bool ListBox::keyStateChanged (const bool isKeyDown)
  646. {
  647. return isKeyDown
  648. && (KeyPress::isKeyCurrentlyDown (KeyPress::upKey)
  649. || KeyPress::isKeyCurrentlyDown (KeyPress::pageUpKey)
  650. || KeyPress::isKeyCurrentlyDown (KeyPress::downKey)
  651. || KeyPress::isKeyCurrentlyDown (KeyPress::pageDownKey)
  652. || KeyPress::isKeyCurrentlyDown (KeyPress::homeKey)
  653. || KeyPress::isKeyCurrentlyDown (KeyPress::endKey)
  654. || KeyPress::isKeyCurrentlyDown (KeyPress::returnKey));
  655. }
  656. void ListBox::mouseWheelMove (const MouseEvent& e, const MouseWheelDetails& wheel)
  657. {
  658. bool eventWasUsed = false;
  659. if (wheel.deltaX != 0 && viewport->getHorizontalScrollBar()->isVisible())
  660. {
  661. eventWasUsed = true;
  662. viewport->getHorizontalScrollBar()->mouseWheelMove (e, wheel);
  663. }
  664. if (wheel.deltaY != 0 && viewport->getVerticalScrollBar()->isVisible())
  665. {
  666. eventWasUsed = true;
  667. viewport->getVerticalScrollBar()->mouseWheelMove (e, wheel);
  668. }
  669. if (! eventWasUsed)
  670. Component::mouseWheelMove (e, wheel);
  671. }
  672. void ListBox::mouseUp (const MouseEvent& e)
  673. {
  674. if (e.mouseWasClicked() && model != nullptr)
  675. model->backgroundClicked (e);
  676. }
  677. //==============================================================================
  678. void ListBox::setRowHeight (const int newHeight)
  679. {
  680. rowHeight = jmax (1, newHeight);
  681. viewport->setSingleStepSizes (20, rowHeight);
  682. updateContent();
  683. }
  684. int ListBox::getNumRowsOnScreen() const noexcept
  685. {
  686. return viewport->getMaximumVisibleHeight() / rowHeight;
  687. }
  688. void ListBox::setMinimumContentWidth (const int newMinimumWidth)
  689. {
  690. minimumRowWidth = newMinimumWidth;
  691. updateContent();
  692. }
  693. int ListBox::getVisibleContentWidth() const noexcept
  694. {
  695. return viewport->getMaximumVisibleWidth();
  696. }
  697. ScrollBar* ListBox::getVerticalScrollBar() const noexcept
  698. {
  699. return viewport->getVerticalScrollBar();
  700. }
  701. ScrollBar* ListBox::getHorizontalScrollBar() const noexcept
  702. {
  703. return viewport->getHorizontalScrollBar();
  704. }
  705. void ListBox::colourChanged()
  706. {
  707. setOpaque (findColour (backgroundColourId).isOpaque());
  708. viewport->setOpaque (isOpaque());
  709. repaint();
  710. }
  711. void ListBox::parentHierarchyChanged()
  712. {
  713. colourChanged();
  714. }
  715. void ListBox::setOutlineThickness (const int newThickness)
  716. {
  717. outlineThickness = newThickness;
  718. resized();
  719. }
  720. void ListBox::setHeaderComponent (Component* const newHeaderComponent)
  721. {
  722. if (headerComponent != newHeaderComponent)
  723. {
  724. headerComponent = newHeaderComponent;
  725. addAndMakeVisible (newHeaderComponent);
  726. ListBox::resized();
  727. }
  728. }
  729. void ListBox::repaintRow (const int rowNumber) noexcept
  730. {
  731. repaint (getRowPosition (rowNumber, true));
  732. }
  733. Image ListBox::createSnapshotOfSelectedRows (int& imageX, int& imageY)
  734. {
  735. Rectangle<int> imageArea;
  736. const int firstRow = getRowContainingPosition (0, viewport->getY());
  737. for (int i = getNumRowsOnScreen() + 2; --i >= 0;)
  738. {
  739. Component* rowComp = viewport->getComponentForRowIfOnscreen (firstRow + i);
  740. if (rowComp != nullptr && isRowSelected (firstRow + i))
  741. {
  742. const Point<int> pos (getLocalPoint (rowComp, Point<int>()));
  743. const Rectangle<int> rowRect (pos.getX(), pos.getY(), rowComp->getWidth(), rowComp->getHeight());
  744. imageArea = imageArea.getUnion (rowRect);
  745. }
  746. }
  747. imageArea = imageArea.getIntersection (getLocalBounds());
  748. imageX = imageArea.getX();
  749. imageY = imageArea.getY();
  750. Image snapshot (Image::ARGB, imageArea.getWidth(), imageArea.getHeight(), true);
  751. for (int i = getNumRowsOnScreen() + 2; --i >= 0;)
  752. {
  753. Component* rowComp = viewport->getComponentForRowIfOnscreen (firstRow + i);
  754. if (rowComp != nullptr && isRowSelected (firstRow + i))
  755. {
  756. Graphics g (snapshot);
  757. g.setOrigin (getLocalPoint (rowComp, Point<int>()) - imageArea.getPosition());
  758. if (g.reduceClipRegion (rowComp->getLocalBounds()))
  759. {
  760. g.beginTransparencyLayer (0.6f);
  761. rowComp->paintEntireComponent (g, false);
  762. g.endTransparencyLayer();
  763. }
  764. }
  765. }
  766. return snapshot;
  767. }
  768. void ListBox::startDragAndDrop (const MouseEvent& e, const var& dragDescription, bool allowDraggingToOtherWindows)
  769. {
  770. if (DragAndDropContainer* const dragContainer = DragAndDropContainer::findParentDragContainerFor (this))
  771. {
  772. int x, y;
  773. Image dragImage (createSnapshotOfSelectedRows (x, y));
  774. MouseEvent e2 (e.getEventRelativeTo (this));
  775. const Point<int> p (x - e2.x, y - e2.y);
  776. dragContainer->startDragging (dragDescription, this, dragImage, allowDraggingToOtherWindows, &p);
  777. }
  778. else
  779. {
  780. // to be able to do a drag-and-drop operation, the listbox needs to
  781. // be inside a component which is also a DragAndDropContainer.
  782. jassertfalse;
  783. }
  784. }
  785. //==============================================================================
  786. Component* ListBoxModel::refreshComponentForRow (int, bool, Component* existingComponentToUpdate)
  787. {
  788. (void) existingComponentToUpdate;
  789. jassert (existingComponentToUpdate == nullptr); // indicates a failure in the code that recycles the components
  790. return nullptr;
  791. }
  792. void ListBoxModel::listBoxItemClicked (int, const MouseEvent&) {}
  793. void ListBoxModel::listBoxItemDoubleClicked (int, const MouseEvent&) {}
  794. void ListBoxModel::backgroundClicked (const MouseEvent&) {}
  795. void ListBoxModel::selectedRowsChanged (int) {}
  796. void ListBoxModel::deleteKeyPressed (int) {}
  797. void ListBoxModel::returnKeyPressed (int) {}
  798. void ListBoxModel::listWasScrolled() {}
  799. var ListBoxModel::getDragSourceDescription (const SparseSet<int>&) { return var(); }
  800. String ListBoxModel::getTooltipForRow (int) { return String::empty; }
  801. MouseCursor ListBoxModel::getMouseCursorForRow (int) { return MouseCursor::NormalCursor; }