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.

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