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.

1255 lines
40KB

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