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.

953 lines
29KB

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