juce_ListBox.cpp 30 KB

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