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.

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