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.

1192 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::setModel (ListBoxModel* const newModel)
  423. {
  424. if (model != newModel)
  425. {
  426. model = newModel;
  427. repaint();
  428. updateContent();
  429. }
  430. }
  431. void ListBox::setMultipleSelectionEnabled (bool b) noexcept { multipleSelection = b; }
  432. void ListBox::setClickingTogglesRowSelection (bool b) noexcept { alwaysFlipSelection = b; }
  433. void ListBox::setRowSelectedOnMouseDown (bool b) noexcept { selectOnMouseDown = b; }
  434. void ListBox::setMouseMoveSelectsRows (bool b)
  435. {
  436. if (b)
  437. {
  438. if (mouseMoveSelector == nullptr)
  439. mouseMoveSelector.reset (new ListBoxMouseMoveSelector (*this));
  440. }
  441. else
  442. {
  443. mouseMoveSelector.reset();
  444. }
  445. }
  446. //==============================================================================
  447. void ListBox::paint (Graphics& g)
  448. {
  449. if (! hasDoneInitialUpdate)
  450. updateContent();
  451. g.fillAll (findColour (backgroundColourId));
  452. }
  453. void ListBox::paintOverChildren (Graphics& g)
  454. {
  455. if (outlineThickness > 0)
  456. {
  457. g.setColour (findColour (outlineColourId));
  458. g.drawRect (getLocalBounds(), outlineThickness);
  459. }
  460. }
  461. void ListBox::resized()
  462. {
  463. viewport->setBoundsInset (BorderSize<int> (outlineThickness + (headerComponent != nullptr ? headerComponent->getHeight() : 0),
  464. outlineThickness, outlineThickness, outlineThickness));
  465. viewport->setSingleStepSizes (20, getRowHeight());
  466. viewport->updateVisibleArea (false);
  467. }
  468. void ListBox::visibilityChanged()
  469. {
  470. viewport->updateVisibleArea (true);
  471. }
  472. Viewport* ListBox::getViewport() const noexcept
  473. {
  474. return viewport.get();
  475. }
  476. //==============================================================================
  477. void ListBox::updateContent()
  478. {
  479. hasDoneInitialUpdate = true;
  480. totalItems = (model != nullptr) ? model->getNumRows() : 0;
  481. bool selectionChanged = false;
  482. if (selected.size() > 0 && selected [selected.size() - 1] >= totalItems)
  483. {
  484. selected.removeRange ({ totalItems, std::numeric_limits<int>::max() });
  485. lastRowSelected = getSelectedRow (0);
  486. selectionChanged = true;
  487. }
  488. viewport->updateVisibleArea (isVisible());
  489. viewport->resized();
  490. if (selectionChanged)
  491. {
  492. if (model != nullptr)
  493. model->selectedRowsChanged (lastRowSelected);
  494. if (auto* handler = getAccessibilityHandler())
  495. handler->notifyAccessibilityEvent (AccessibilityEvent::rowSelectionChanged);
  496. }
  497. }
  498. //==============================================================================
  499. void ListBox::selectRow (int row, bool dontScroll, bool deselectOthersFirst)
  500. {
  501. selectRowInternal (row, dontScroll, deselectOthersFirst, false);
  502. }
  503. void ListBox::selectRowInternal (const int row,
  504. bool dontScroll,
  505. bool deselectOthersFirst,
  506. bool isMouseClick)
  507. {
  508. if (! multipleSelection)
  509. deselectOthersFirst = true;
  510. if ((! isRowSelected (row))
  511. || (deselectOthersFirst && getNumSelectedRows() > 1))
  512. {
  513. if (isPositiveAndBelow (row, totalItems))
  514. {
  515. if (deselectOthersFirst)
  516. selected.clear();
  517. selected.addRange ({ row, row + 1 });
  518. if (getHeight() == 0 || getWidth() == 0)
  519. dontScroll = true;
  520. viewport->selectRow (row, getRowHeight(), dontScroll,
  521. lastRowSelected, totalItems, isMouseClick);
  522. lastRowSelected = row;
  523. model->selectedRowsChanged (row);
  524. if (auto* handler = getAccessibilityHandler())
  525. handler->notifyAccessibilityEvent (AccessibilityEvent::rowSelectionChanged);
  526. }
  527. else
  528. {
  529. if (deselectOthersFirst)
  530. deselectAllRows();
  531. }
  532. }
  533. }
  534. void ListBox::deselectRow (const int row)
  535. {
  536. if (selected.contains (row))
  537. {
  538. selected.removeRange ({ row, row + 1 });
  539. if (row == lastRowSelected)
  540. lastRowSelected = getSelectedRow (0);
  541. viewport->updateContents();
  542. model->selectedRowsChanged (lastRowSelected);
  543. if (auto* handler = getAccessibilityHandler())
  544. handler->notifyAccessibilityEvent (AccessibilityEvent::rowSelectionChanged);
  545. }
  546. }
  547. void ListBox::setSelectedRows (const SparseSet<int>& setOfRowsToBeSelected,
  548. const NotificationType sendNotificationEventToModel)
  549. {
  550. selected = setOfRowsToBeSelected;
  551. selected.removeRange ({ totalItems, std::numeric_limits<int>::max() });
  552. if (! isRowSelected (lastRowSelected))
  553. lastRowSelected = getSelectedRow (0);
  554. viewport->updateContents();
  555. if (model != nullptr && sendNotificationEventToModel == sendNotification)
  556. model->selectedRowsChanged (lastRowSelected);
  557. if (auto* handler = getAccessibilityHandler())
  558. handler->notifyAccessibilityEvent (AccessibilityEvent::rowSelectionChanged);
  559. }
  560. SparseSet<int> ListBox::getSelectedRows() const
  561. {
  562. return selected;
  563. }
  564. void ListBox::selectRangeOfRows (int firstRow, int lastRow, bool dontScrollToShowThisRange)
  565. {
  566. if (multipleSelection && (firstRow != lastRow))
  567. {
  568. const int numRows = totalItems - 1;
  569. firstRow = jlimit (0, jmax (0, numRows), firstRow);
  570. lastRow = jlimit (0, jmax (0, numRows), lastRow);
  571. selected.addRange ({ jmin (firstRow, lastRow),
  572. jmax (firstRow, lastRow) + 1 });
  573. selected.removeRange ({ lastRow, lastRow + 1 });
  574. }
  575. selectRowInternal (lastRow, dontScrollToShowThisRange, false, true);
  576. }
  577. void ListBox::flipRowSelection (const int row)
  578. {
  579. if (isRowSelected (row))
  580. deselectRow (row);
  581. else
  582. selectRowInternal (row, false, false, true);
  583. }
  584. void ListBox::deselectAllRows()
  585. {
  586. if (! selected.isEmpty())
  587. {
  588. selected.clear();
  589. lastRowSelected = -1;
  590. viewport->updateContents();
  591. if (model != nullptr)
  592. model->selectedRowsChanged (lastRowSelected);
  593. if (auto* handler = getAccessibilityHandler())
  594. handler->notifyAccessibilityEvent (AccessibilityEvent::rowSelectionChanged);
  595. }
  596. }
  597. void ListBox::selectRowsBasedOnModifierKeys (const int row,
  598. ModifierKeys mods,
  599. const bool isMouseUpEvent)
  600. {
  601. if (multipleSelection && (mods.isCommandDown() || alwaysFlipSelection))
  602. {
  603. flipRowSelection (row);
  604. }
  605. else if (multipleSelection && mods.isShiftDown() && lastRowSelected >= 0)
  606. {
  607. selectRangeOfRows (lastRowSelected, row);
  608. }
  609. else if ((! mods.isPopupMenu()) || ! isRowSelected (row))
  610. {
  611. selectRowInternal (row, false, ! (multipleSelection && (! isMouseUpEvent) && isRowSelected (row)), true);
  612. }
  613. }
  614. int ListBox::getNumSelectedRows() const
  615. {
  616. return selected.size();
  617. }
  618. int ListBox::getSelectedRow (const int index) const
  619. {
  620. return (isPositiveAndBelow (index, selected.size()))
  621. ? selected [index] : -1;
  622. }
  623. bool ListBox::isRowSelected (const int row) const
  624. {
  625. return selected.contains (row);
  626. }
  627. int ListBox::getLastRowSelected() const
  628. {
  629. return isRowSelected (lastRowSelected) ? lastRowSelected : -1;
  630. }
  631. //==============================================================================
  632. int ListBox::getRowContainingPosition (const int x, const int y) const noexcept
  633. {
  634. if (isPositiveAndBelow (x, getWidth()))
  635. {
  636. const int row = (viewport->getViewPositionY() + y - viewport->getY()) / rowHeight;
  637. if (isPositiveAndBelow (row, totalItems))
  638. return row;
  639. }
  640. return -1;
  641. }
  642. int ListBox::getInsertionIndexForPosition (const int x, const int y) const noexcept
  643. {
  644. if (isPositiveAndBelow (x, getWidth()))
  645. return jlimit (0, totalItems, (viewport->getViewPositionY() + y + rowHeight / 2 - viewport->getY()) / rowHeight);
  646. return -1;
  647. }
  648. Component* ListBox::getComponentForRowNumber (const int row) const noexcept
  649. {
  650. if (auto* listRowComp = viewport->getComponentForRowIfOnscreen (row))
  651. return listRowComp->customComponent.get();
  652. return nullptr;
  653. }
  654. int ListBox::getRowNumberOfComponent (Component* const rowComponent) const noexcept
  655. {
  656. return viewport->getRowNumberOfComponent (rowComponent);
  657. }
  658. Rectangle<int> ListBox::getRowPosition (int rowNumber, bool relativeToComponentTopLeft) const noexcept
  659. {
  660. auto y = viewport->getY() + rowHeight * rowNumber;
  661. if (relativeToComponentTopLeft)
  662. y -= viewport->getViewPositionY();
  663. return { viewport->getX(), y,
  664. viewport->getViewedComponent()->getWidth(), rowHeight };
  665. }
  666. void ListBox::setVerticalPosition (const double proportion)
  667. {
  668. auto offscreen = viewport->getViewedComponent()->getHeight() - viewport->getHeight();
  669. viewport->setViewPosition (viewport->getViewPositionX(),
  670. jmax (0, roundToInt (proportion * offscreen)));
  671. }
  672. double ListBox::getVerticalPosition() const
  673. {
  674. auto offscreen = viewport->getViewedComponent()->getHeight() - viewport->getHeight();
  675. return offscreen > 0 ? viewport->getViewPositionY() / (double) offscreen
  676. : 0;
  677. }
  678. int ListBox::getVisibleRowWidth() const noexcept
  679. {
  680. return viewport->getViewWidth();
  681. }
  682. void ListBox::scrollToEnsureRowIsOnscreen (const int row)
  683. {
  684. viewport->scrollToEnsureRowIsOnscreen (row, getRowHeight());
  685. }
  686. //==============================================================================
  687. bool ListBox::keyPressed (const KeyPress& key)
  688. {
  689. const int numVisibleRows = viewport->getHeight() / getRowHeight();
  690. const bool multiple = multipleSelection
  691. && lastRowSelected >= 0
  692. && key.getModifiers().isShiftDown();
  693. if (key.isKeyCode (KeyPress::upKey))
  694. {
  695. if (multiple)
  696. selectRangeOfRows (lastRowSelected, lastRowSelected - 1);
  697. else
  698. selectRow (jmax (0, lastRowSelected - 1));
  699. }
  700. else if (key.isKeyCode (KeyPress::downKey))
  701. {
  702. if (multiple)
  703. selectRangeOfRows (lastRowSelected, lastRowSelected + 1);
  704. else
  705. selectRow (jmin (totalItems - 1, jmax (0, lastRowSelected + 1)));
  706. }
  707. else if (key.isKeyCode (KeyPress::pageUpKey))
  708. {
  709. if (multiple)
  710. selectRangeOfRows (lastRowSelected, lastRowSelected - numVisibleRows);
  711. else
  712. selectRow (jmax (0, jmax (0, lastRowSelected) - numVisibleRows));
  713. }
  714. else if (key.isKeyCode (KeyPress::pageDownKey))
  715. {
  716. if (multiple)
  717. selectRangeOfRows (lastRowSelected, lastRowSelected + numVisibleRows);
  718. else
  719. selectRow (jmin (totalItems - 1, jmax (0, lastRowSelected) + numVisibleRows));
  720. }
  721. else if (key.isKeyCode (KeyPress::homeKey))
  722. {
  723. if (multiple)
  724. selectRangeOfRows (lastRowSelected, 0);
  725. else
  726. selectRow (0);
  727. }
  728. else if (key.isKeyCode (KeyPress::endKey))
  729. {
  730. if (multiple)
  731. selectRangeOfRows (lastRowSelected, totalItems - 1);
  732. else
  733. selectRow (totalItems - 1);
  734. }
  735. else if (key.isKeyCode (KeyPress::returnKey) && isRowSelected (lastRowSelected))
  736. {
  737. if (model != nullptr)
  738. model->returnKeyPressed (lastRowSelected);
  739. }
  740. else if ((key.isKeyCode (KeyPress::deleteKey) || key.isKeyCode (KeyPress::backspaceKey))
  741. && isRowSelected (lastRowSelected))
  742. {
  743. if (model != nullptr)
  744. model->deleteKeyPressed (lastRowSelected);
  745. }
  746. else if (multipleSelection && key == KeyPress ('a', ModifierKeys::commandModifier, 0))
  747. {
  748. selectRangeOfRows (0, std::numeric_limits<int>::max());
  749. }
  750. else
  751. {
  752. return false;
  753. }
  754. return true;
  755. }
  756. bool ListBox::keyStateChanged (const bool isKeyDown)
  757. {
  758. return isKeyDown
  759. && (KeyPress::isKeyCurrentlyDown (KeyPress::upKey)
  760. || KeyPress::isKeyCurrentlyDown (KeyPress::pageUpKey)
  761. || KeyPress::isKeyCurrentlyDown (KeyPress::downKey)
  762. || KeyPress::isKeyCurrentlyDown (KeyPress::pageDownKey)
  763. || KeyPress::isKeyCurrentlyDown (KeyPress::homeKey)
  764. || KeyPress::isKeyCurrentlyDown (KeyPress::endKey)
  765. || KeyPress::isKeyCurrentlyDown (KeyPress::returnKey));
  766. }
  767. void ListBox::mouseWheelMove (const MouseEvent& e, const MouseWheelDetails& wheel)
  768. {
  769. bool eventWasUsed = false;
  770. if (wheel.deltaX != 0.0f && getHorizontalScrollBar().isVisible())
  771. {
  772. eventWasUsed = true;
  773. getHorizontalScrollBar().mouseWheelMove (e, wheel);
  774. }
  775. if (wheel.deltaY != 0.0f && getVerticalScrollBar().isVisible())
  776. {
  777. eventWasUsed = true;
  778. getVerticalScrollBar().mouseWheelMove (e, wheel);
  779. }
  780. if (! eventWasUsed)
  781. Component::mouseWheelMove (e, wheel);
  782. }
  783. void ListBox::mouseUp (const MouseEvent& e)
  784. {
  785. if (e.mouseWasClicked() && model != nullptr)
  786. model->backgroundClicked (e);
  787. }
  788. //==============================================================================
  789. void ListBox::setRowHeight (const int newHeight)
  790. {
  791. rowHeight = jmax (1, newHeight);
  792. viewport->setSingleStepSizes (20, rowHeight);
  793. updateContent();
  794. }
  795. int ListBox::getNumRowsOnScreen() const noexcept
  796. {
  797. return viewport->getMaximumVisibleHeight() / rowHeight;
  798. }
  799. void ListBox::setMinimumContentWidth (const int newMinimumWidth)
  800. {
  801. minimumRowWidth = newMinimumWidth;
  802. updateContent();
  803. }
  804. int ListBox::getVisibleContentWidth() const noexcept { return viewport->getMaximumVisibleWidth(); }
  805. ScrollBar& ListBox::getVerticalScrollBar() const noexcept { return viewport->getVerticalScrollBar(); }
  806. ScrollBar& ListBox::getHorizontalScrollBar() const noexcept { return viewport->getHorizontalScrollBar(); }
  807. void ListBox::colourChanged()
  808. {
  809. setOpaque (findColour (backgroundColourId).isOpaque());
  810. viewport->setOpaque (isOpaque());
  811. repaint();
  812. }
  813. void ListBox::parentHierarchyChanged()
  814. {
  815. colourChanged();
  816. }
  817. void ListBox::setOutlineThickness (int newThickness)
  818. {
  819. outlineThickness = newThickness;
  820. resized();
  821. }
  822. void ListBox::setHeaderComponent (std::unique_ptr<Component> newHeaderComponent)
  823. {
  824. headerComponent = std::move (newHeaderComponent);
  825. addAndMakeVisible (headerComponent.get());
  826. ListBox::resized();
  827. invalidateAccessibilityHandler();
  828. }
  829. bool ListBox::hasAccessibleHeaderComponent() const
  830. {
  831. return headerComponent != nullptr
  832. && headerComponent->getAccessibilityHandler() != nullptr;
  833. }
  834. void ListBox::repaintRow (const int rowNumber) noexcept
  835. {
  836. repaint (getRowPosition (rowNumber, true));
  837. }
  838. ScaledImage ListBox::createSnapshotOfRows (const SparseSet<int>& rows, int& imageX, int& imageY)
  839. {
  840. Rectangle<int> imageArea;
  841. auto firstRow = getRowContainingPosition (0, viewport->getY());
  842. for (int i = getNumRowsOnScreen() + 2; --i >= 0;)
  843. {
  844. if (rows.contains (firstRow + i))
  845. {
  846. if (auto* rowComp = viewport->getComponentForRowIfOnscreen (firstRow + i))
  847. {
  848. auto pos = getLocalPoint (rowComp, Point<int>());
  849. imageArea = imageArea.getUnion ({ pos.x, pos.y, rowComp->getWidth(), rowComp->getHeight() });
  850. }
  851. }
  852. }
  853. imageArea = imageArea.getIntersection (getLocalBounds());
  854. imageX = imageArea.getX();
  855. imageY = imageArea.getY();
  856. const auto additionalScale = 2.0f;
  857. const auto listScale = Component::getApproximateScaleFactorForComponent (this) * additionalScale;
  858. Image snapshot (Image::ARGB,
  859. roundToInt ((float) imageArea.getWidth() * listScale),
  860. roundToInt ((float) imageArea.getHeight() * listScale),
  861. true);
  862. for (int i = getNumRowsOnScreen() + 2; --i >= 0;)
  863. {
  864. if (rows.contains (firstRow + i))
  865. {
  866. if (auto* rowComp = viewport->getComponentForRowIfOnscreen (firstRow + i))
  867. {
  868. Graphics g (snapshot);
  869. g.setOrigin ((getLocalPoint (rowComp, Point<int>()) - imageArea.getPosition()) * additionalScale);
  870. const auto rowScale = Component::getApproximateScaleFactorForComponent (rowComp) * additionalScale;
  871. if (g.reduceClipRegion (rowComp->getLocalBounds() * rowScale))
  872. {
  873. g.beginTransparencyLayer (0.6f);
  874. g.addTransform (AffineTransform::scale (rowScale));
  875. rowComp->paintEntireComponent (g, false);
  876. g.endTransparencyLayer();
  877. }
  878. }
  879. }
  880. }
  881. return { snapshot, additionalScale };
  882. }
  883. void ListBox::startDragAndDrop (const MouseEvent& e, const SparseSet<int>& rowsToDrag, const var& dragDescription, bool allowDraggingToOtherWindows)
  884. {
  885. if (auto* dragContainer = DragAndDropContainer::findParentDragContainerFor (this))
  886. {
  887. int x, y;
  888. auto dragImage = createSnapshotOfRows (rowsToDrag, x, y);
  889. auto p = Point<int> (x, y) - e.getEventRelativeTo (this).position.toInt();
  890. dragContainer->startDragging (dragDescription, this, dragImage, allowDraggingToOtherWindows, &p, &e.source);
  891. }
  892. else
  893. {
  894. // to be able to do a drag-and-drop operation, the listbox needs to
  895. // be inside a component which is also a DragAndDropContainer.
  896. jassertfalse;
  897. }
  898. }
  899. std::unique_ptr<AccessibilityHandler> ListBox::createAccessibilityHandler()
  900. {
  901. class TableInterface : public AccessibilityTableInterface
  902. {
  903. public:
  904. explicit TableInterface (ListBox& listBoxToWrap)
  905. : listBox (listBoxToWrap)
  906. {
  907. }
  908. int getNumRows() const override
  909. {
  910. if (listBox.model == nullptr)
  911. return 0;
  912. const auto numRows = listBox.model->getNumRows();
  913. if (listBox.hasAccessibleHeaderComponent())
  914. return numRows + 1;
  915. return numRows;
  916. }
  917. int getNumColumns() const override
  918. {
  919. return 1;
  920. }
  921. const AccessibilityHandler* getCellHandler (int row, int) const override
  922. {
  923. if (auto* headerHandler = getHeaderHandler())
  924. {
  925. if (row == 0)
  926. return headerHandler;
  927. --row;
  928. }
  929. if (auto* rowComponent = listBox.viewport->getComponentForRow (row))
  930. return rowComponent->getAccessibilityHandler();
  931. return nullptr;
  932. }
  933. private:
  934. const AccessibilityHandler* getHeaderHandler() const
  935. {
  936. if (listBox.hasAccessibleHeaderComponent())
  937. return listBox.headerComponent->getAccessibilityHandler();
  938. return nullptr;
  939. }
  940. ListBox& listBox;
  941. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TableInterface)
  942. };
  943. return std::make_unique<AccessibilityHandler> (*this,
  944. AccessibilityRole::list,
  945. AccessibilityActions{},
  946. AccessibilityHandler::Interfaces { std::make_unique<TableInterface> (*this) });
  947. }
  948. //==============================================================================
  949. Component* ListBoxModel::refreshComponentForRow (int, bool, Component* existingComponentToUpdate)
  950. {
  951. ignoreUnused (existingComponentToUpdate);
  952. jassert (existingComponentToUpdate == nullptr); // indicates a failure in the code that recycles the components
  953. return nullptr;
  954. }
  955. String ListBoxModel::getNameForRow (int rowNumber) { return "Row " + String (rowNumber + 1); }
  956. void ListBoxModel::listBoxItemClicked (int, const MouseEvent&) {}
  957. void ListBoxModel::listBoxItemDoubleClicked (int, const MouseEvent&) {}
  958. void ListBoxModel::backgroundClicked (const MouseEvent&) {}
  959. void ListBoxModel::selectedRowsChanged (int) {}
  960. void ListBoxModel::deleteKeyPressed (int) {}
  961. void ListBoxModel::returnKeyPressed (int) {}
  962. void ListBoxModel::listWasScrolled() {}
  963. var ListBoxModel::getDragSourceDescription (const SparseSet<int>&) { return {}; }
  964. String ListBoxModel::getTooltipForRow (int) { return {}; }
  965. MouseCursor ListBoxModel::getMouseCursorForRow (int) { return MouseCursor::NormalCursor; }
  966. } // namespace juce