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.

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