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.

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