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.

949 lines
29KB

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