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.

974 lines
30KB

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