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.

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