The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1066 lines
33KB

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