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.

1172 lines
37KB

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