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.

1067 lines
33KB

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