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.

1230 lines
39KB

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