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