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.

1224 lines
38KB

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