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.

962 lines
29KB

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