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.

1185 lines
37KB

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