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.

1170 lines
37KB

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