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.

951 lines
29KB

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