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.

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