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.

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