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.

1232 lines
38KB

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