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.

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