1 /*
2 ==============================================================================
3
4 This file is part of the JUCE library.
5 Copyright (c) 2020 - Raw Material Software Limited
6
7 JUCE is an open source library subject to commercial or open-source
8 licensing.
9
10 By using JUCE, you agree to the terms of both the JUCE 6 End-User License
11 Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
12
13 End User License Agreement: www.juce.com/juce-6-licence
14 Privacy Policy: www.juce.com/juce-privacy-policy
15
16 Or: You may also use this code under the terms of the GPL v3 (see
17 www.gnu.org/licenses).
18
19 JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
20 EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
21 DISCLAIMED.
22
23 ==============================================================================
24 */
25
26 namespace juce
27 {
28
29 class ListBox::RowComponent : public Component,
30 public TooltipClient
31 {
32 public:
RowComponent(ListBox & lb)33 RowComponent (ListBox& lb) : owner (lb) {}
34
paint(Graphics & g)35 void paint (Graphics& g) override
36 {
37 if (auto* m = owner.getModel())
38 m->paintListBoxItem (row, g, getWidth(), getHeight(), selected);
39 }
40
update(const int newRow,const bool nowSelected)41 void update (const int newRow, const bool nowSelected)
42 {
43 if (row != newRow || selected != nowSelected)
44 {
45 repaint();
46 row = newRow;
47 selected = nowSelected;
48 }
49
50 if (auto* m = owner.getModel())
51 {
52 setMouseCursor (m->getMouseCursorForRow (row));
53
54 customComponent.reset (m->refreshComponentForRow (newRow, nowSelected, customComponent.release()));
55
56 if (customComponent != nullptr)
57 {
58 addAndMakeVisible (customComponent.get());
59 customComponent->setBounds (getLocalBounds());
60 }
61 }
62 }
63
performSelection(const MouseEvent & e,bool isMouseUp)64 void performSelection (const MouseEvent& e, bool isMouseUp)
65 {
66 owner.selectRowsBasedOnModifierKeys (row, e.mods, isMouseUp);
67
68 if (auto* m = owner.getModel())
69 m->listBoxItemClicked (row, e);
70 }
71
isInDragToScrollViewport() const72 bool isInDragToScrollViewport() const noexcept
73 {
74 if (auto* vp = owner.getViewport())
75 return vp->isScrollOnDragEnabled() && (vp->canScrollVertically() || vp->canScrollHorizontally());
76
77 return false;
78 }
79
mouseDown(const MouseEvent & e)80 void mouseDown (const MouseEvent& e) override
81 {
82 isDragging = false;
83 isDraggingToScroll = false;
84 selectRowOnMouseUp = false;
85
86 if (isEnabled())
87 {
88 if (owner.selectOnMouseDown && ! (selected || isInDragToScrollViewport()))
89 performSelection (e, false);
90 else
91 selectRowOnMouseUp = true;
92 }
93 }
94
mouseUp(const MouseEvent & e)95 void mouseUp (const MouseEvent& e) override
96 {
97 if (isEnabled() && selectRowOnMouseUp && ! (isDragging || isDraggingToScroll))
98 performSelection (e, true);
99 }
100
mouseDoubleClick(const MouseEvent & e)101 void mouseDoubleClick (const MouseEvent& e) override
102 {
103 if (isEnabled())
104 if (auto* m = owner.getModel())
105 m->listBoxItemDoubleClicked (row, e);
106 }
107
mouseDrag(const MouseEvent & e)108 void mouseDrag (const MouseEvent& e) override
109 {
110 if (auto* m = owner.getModel())
111 {
112 if (isEnabled() && e.mouseWasDraggedSinceMouseDown() && ! isDragging)
113 {
114 SparseSet<int> rowsToDrag;
115
116 if (owner.selectOnMouseDown || owner.isRowSelected (row))
117 rowsToDrag = owner.getSelectedRows();
118 else
119 rowsToDrag.addRange (Range<int>::withStartAndLength (row, 1));
120
121 if (rowsToDrag.size() > 0)
122 {
123 auto dragDescription = m->getDragSourceDescription (rowsToDrag);
124
125 if (! (dragDescription.isVoid() || (dragDescription.isString() && dragDescription.toString().isEmpty())))
126 {
127 isDragging = true;
128 owner.startDragAndDrop (e, rowsToDrag, dragDescription, true);
129 }
130 }
131 }
132 }
133
134 if (! isDraggingToScroll)
135 if (auto* vp = owner.getViewport())
136 isDraggingToScroll = vp->isCurrentlyScrollingOnDrag();
137 }
138
resized()139 void resized() override
140 {
141 if (customComponent != nullptr)
142 customComponent->setBounds (getLocalBounds());
143 }
144
getTooltip()145 String getTooltip() override
146 {
147 if (auto* m = owner.getModel())
148 return m->getTooltipForRow (row);
149
150 return {};
151 }
152
153 ListBox& owner;
154 std::unique_ptr<Component> customComponent;
155 int row = -1;
156 bool selected = false, isDragging = false, isDraggingToScroll = false, selectRowOnMouseUp = false;
157
158 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (RowComponent)
159 };
160
161
162 //==============================================================================
163 class ListBox::ListViewport : public Viewport
164 {
165 public:
ListViewport(ListBox & lb)166 ListViewport (ListBox& lb) : owner (lb)
167 {
168 setWantsKeyboardFocus (false);
169
170 auto content = new Component();
171 setViewedComponent (content);
172 content->setWantsKeyboardFocus (false);
173 }
174
getComponentForRow(const int row) const175 RowComponent* getComponentForRow (const int row) const noexcept
176 {
177 return rows [row % jmax (1, rows.size())];
178 }
179
getComponentForRowIfOnscreen(const int row) const180 RowComponent* getComponentForRowIfOnscreen (const int row) const noexcept
181 {
182 return (row >= firstIndex && row < firstIndex + rows.size())
183 ? getComponentForRow (row) : nullptr;
184 }
185
getRowNumberOfComponent(Component * const rowComponent) const186 int getRowNumberOfComponent (Component* const rowComponent) const noexcept
187 {
188 const int index = getViewedComponent()->getIndexOfChildComponent (rowComponent);
189 const int num = rows.size();
190
191 for (int i = num; --i >= 0;)
192 if (((firstIndex + i) % jmax (1, num)) == index)
193 return firstIndex + i;
194
195 return -1;
196 }
197
visibleAreaChanged(const Rectangle<int> &)198 void visibleAreaChanged (const Rectangle<int>&) override
199 {
200 updateVisibleArea (true);
201
202 if (auto* m = owner.getModel())
203 m->listWasScrolled();
204 }
205
updateVisibleArea(const bool makeSureItUpdatesContent)206 void updateVisibleArea (const bool makeSureItUpdatesContent)
207 {
208 hasUpdated = false;
209
210 auto& content = *getViewedComponent();
211 auto newX = content.getX();
212 auto newY = content.getY();
213 auto newW = jmax (owner.minimumRowWidth, getMaximumVisibleWidth());
214 auto newH = owner.totalItems * owner.getRowHeight();
215
216 if (newY + newH < getMaximumVisibleHeight() && newH > getMaximumVisibleHeight())
217 newY = getMaximumVisibleHeight() - newH;
218
219 content.setBounds (newX, newY, newW, newH);
220
221 if (makeSureItUpdatesContent && ! hasUpdated)
222 updateContents();
223 }
224
updateContents()225 void updateContents()
226 {
227 hasUpdated = true;
228 auto rowH = owner.getRowHeight();
229 auto& content = *getViewedComponent();
230
231 if (rowH > 0)
232 {
233 auto y = getViewPositionY();
234 auto w = content.getWidth();
235
236 const int numNeeded = 2 + getMaximumVisibleHeight() / rowH;
237 rows.removeRange (numNeeded, rows.size());
238
239 while (numNeeded > rows.size())
240 {
241 auto newRow = new RowComponent (owner);
242 rows.add (newRow);
243 content.addAndMakeVisible (newRow);
244 }
245
246 firstIndex = y / rowH;
247 firstWholeIndex = (y + rowH - 1) / rowH;
248 lastWholeIndex = (y + getMaximumVisibleHeight() - 1) / rowH;
249
250 for (int i = 0; i < numNeeded; ++i)
251 {
252 const int row = i + firstIndex;
253
254 if (auto* rowComp = getComponentForRow (row))
255 {
256 rowComp->setBounds (0, row * rowH, w, rowH);
257 rowComp->update (row, owner.isRowSelected (row));
258 }
259 }
260 }
261
262 if (owner.headerComponent != nullptr)
263 owner.headerComponent->setBounds (owner.outlineThickness + content.getX(),
264 owner.outlineThickness,
265 jmax (owner.getWidth() - owner.outlineThickness * 2,
266 content.getWidth()),
267 owner.headerComponent->getHeight());
268 }
269
selectRow(const int row,const int rowH,const bool dontScroll,const int lastSelectedRow,const int totalRows,const bool isMouseClick)270 void selectRow (const int row, const int rowH, const bool dontScroll,
271 const int lastSelectedRow, const int totalRows, const bool isMouseClick)
272 {
273 hasUpdated = false;
274
275 if (row < firstWholeIndex && ! dontScroll)
276 {
277 setViewPosition (getViewPositionX(), row * rowH);
278 }
279 else if (row >= lastWholeIndex && ! dontScroll)
280 {
281 const int rowsOnScreen = lastWholeIndex - firstWholeIndex;
282
283 if (row >= lastSelectedRow + rowsOnScreen
284 && rowsOnScreen < totalRows - 1
285 && ! isMouseClick)
286 {
287 setViewPosition (getViewPositionX(),
288 jlimit (0, jmax (0, totalRows - rowsOnScreen), row) * rowH);
289 }
290 else
291 {
292 setViewPosition (getViewPositionX(),
293 jmax (0, (row + 1) * rowH - getMaximumVisibleHeight()));
294 }
295 }
296
297 if (! hasUpdated)
298 updateContents();
299 }
300
scrollToEnsureRowIsOnscreen(const int row,const int rowH)301 void scrollToEnsureRowIsOnscreen (const int row, const int rowH)
302 {
303 if (row < firstWholeIndex)
304 {
305 setViewPosition (getViewPositionX(), row * rowH);
306 }
307 else if (row >= lastWholeIndex)
308 {
309 setViewPosition (getViewPositionX(),
310 jmax (0, (row + 1) * rowH - getMaximumVisibleHeight()));
311 }
312 }
313
paint(Graphics & g)314 void paint (Graphics& g) override
315 {
316 if (isOpaque())
317 g.fillAll (owner.findColour (ListBox::backgroundColourId));
318 }
319
keyPressed(const KeyPress & key)320 bool keyPressed (const KeyPress& key) override
321 {
322 if (Viewport::respondsToKey (key))
323 {
324 const int allowableMods = owner.multipleSelection ? ModifierKeys::shiftModifier : 0;
325
326 if ((key.getModifiers().getRawFlags() & ~allowableMods) == 0)
327 {
328 // we want to avoid these keypresses going to the viewport, and instead allow
329 // them to pass up to our listbox..
330 return false;
331 }
332 }
333
334 return Viewport::keyPressed (key);
335 }
336
337 private:
338 ListBox& owner;
339 OwnedArray<RowComponent> rows;
340 int firstIndex = 0, firstWholeIndex = 0, lastWholeIndex = 0;
341 bool hasUpdated = false;
342
343 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ListViewport)
344 };
345
346 //==============================================================================
347 struct ListBoxMouseMoveSelector : public MouseListener
348 {
ListBoxMouseMoveSelectorjuce::ListBoxMouseMoveSelector349 ListBoxMouseMoveSelector (ListBox& lb) : owner (lb)
350 {
351 owner.addMouseListener (this, true);
352 }
353
~ListBoxMouseMoveSelectorjuce::ListBoxMouseMoveSelector354 ~ListBoxMouseMoveSelector() override
355 {
356 owner.removeMouseListener (this);
357 }
358
mouseMovejuce::ListBoxMouseMoveSelector359 void mouseMove (const MouseEvent& e) override
360 {
361 auto pos = e.getEventRelativeTo (&owner).position.toInt();
362 owner.selectRow (owner.getRowContainingPosition (pos.x, pos.y), true);
363 }
364
mouseExitjuce::ListBoxMouseMoveSelector365 void mouseExit (const MouseEvent& e) override
366 {
367 mouseMove (e);
368 }
369
370 ListBox& owner;
371 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ListBoxMouseMoveSelector)
372 };
373
374
375 //==============================================================================
ListBox(const String & name,ListBoxModel * const m)376 ListBox::ListBox (const String& name, ListBoxModel* const m)
377 : Component (name), model (m)
378 {
379 viewport.reset (new ListViewport (*this));
380 addAndMakeVisible (viewport.get());
381
382 ListBox::setWantsKeyboardFocus (true);
383 ListBox::colourChanged();
384 }
385
~ListBox()386 ListBox::~ListBox()
387 {
388 headerComponent.reset();
389 viewport.reset();
390 }
391
setModel(ListBoxModel * const newModel)392 void ListBox::setModel (ListBoxModel* const newModel)
393 {
394 if (model != newModel)
395 {
396 model = newModel;
397 repaint();
398 updateContent();
399 }
400 }
401
setMultipleSelectionEnabled(bool b)402 void ListBox::setMultipleSelectionEnabled (bool b) noexcept { multipleSelection = b; }
setClickingTogglesRowSelection(bool b)403 void ListBox::setClickingTogglesRowSelection (bool b) noexcept { alwaysFlipSelection = b; }
setRowSelectedOnMouseDown(bool b)404 void ListBox::setRowSelectedOnMouseDown (bool b) noexcept { selectOnMouseDown = b; }
405
setMouseMoveSelectsRows(bool b)406 void ListBox::setMouseMoveSelectsRows (bool b)
407 {
408 if (b)
409 {
410 if (mouseMoveSelector == nullptr)
411 mouseMoveSelector.reset (new ListBoxMouseMoveSelector (*this));
412 }
413 else
414 {
415 mouseMoveSelector.reset();
416 }
417 }
418
419 //==============================================================================
paint(Graphics & g)420 void ListBox::paint (Graphics& g)
421 {
422 if (! hasDoneInitialUpdate)
423 updateContent();
424
425 g.fillAll (findColour (backgroundColourId));
426 }
427
paintOverChildren(Graphics & g)428 void ListBox::paintOverChildren (Graphics& g)
429 {
430 if (outlineThickness > 0)
431 {
432 g.setColour (findColour (outlineColourId));
433 g.drawRect (getLocalBounds(), outlineThickness);
434 }
435 }
436
resized()437 void ListBox::resized()
438 {
439 viewport->setBoundsInset (BorderSize<int> (outlineThickness + (headerComponent != nullptr ? headerComponent->getHeight() : 0),
440 outlineThickness, outlineThickness, outlineThickness));
441
442 viewport->setSingleStepSizes (20, getRowHeight());
443
444 viewport->updateVisibleArea (false);
445 }
446
visibilityChanged()447 void ListBox::visibilityChanged()
448 {
449 viewport->updateVisibleArea (true);
450 }
451
getViewport() const452 Viewport* ListBox::getViewport() const noexcept
453 {
454 return viewport.get();
455 }
456
457 //==============================================================================
updateContent()458 void ListBox::updateContent()
459 {
460 hasDoneInitialUpdate = true;
461 totalItems = (model != nullptr) ? model->getNumRows() : 0;
462
463 bool selectionChanged = false;
464
465 if (selected.size() > 0 && selected [selected.size() - 1] >= totalItems)
466 {
467 selected.removeRange ({ totalItems, std::numeric_limits<int>::max() });
468 lastRowSelected = getSelectedRow (0);
469 selectionChanged = true;
470 }
471
472 viewport->updateVisibleArea (isVisible());
473 viewport->resized();
474
475 if (selectionChanged && model != nullptr)
476 model->selectedRowsChanged (lastRowSelected);
477 }
478
479 //==============================================================================
selectRow(int row,bool dontScroll,bool deselectOthersFirst)480 void ListBox::selectRow (int row, bool dontScroll, bool deselectOthersFirst)
481 {
482 selectRowInternal (row, dontScroll, deselectOthersFirst, false);
483 }
484
selectRowInternal(const int row,bool dontScroll,bool deselectOthersFirst,bool isMouseClick)485 void ListBox::selectRowInternal (const int row,
486 bool dontScroll,
487 bool deselectOthersFirst,
488 bool isMouseClick)
489 {
490 if (! multipleSelection)
491 deselectOthersFirst = true;
492
493 if ((! isRowSelected (row))
494 || (deselectOthersFirst && getNumSelectedRows() > 1))
495 {
496 if (isPositiveAndBelow (row, totalItems))
497 {
498 if (deselectOthersFirst)
499 selected.clear();
500
501 selected.addRange ({ row, row + 1 });
502
503 if (getHeight() == 0 || getWidth() == 0)
504 dontScroll = true;
505
506 viewport->selectRow (row, getRowHeight(), dontScroll,
507 lastRowSelected, totalItems, isMouseClick);
508
509 lastRowSelected = row;
510 model->selectedRowsChanged (row);
511 }
512 else
513 {
514 if (deselectOthersFirst)
515 deselectAllRows();
516 }
517 }
518 }
519
deselectRow(const int row)520 void ListBox::deselectRow (const int row)
521 {
522 if (selected.contains (row))
523 {
524 selected.removeRange ({ row, row + 1 });
525
526 if (row == lastRowSelected)
527 lastRowSelected = getSelectedRow (0);
528
529 viewport->updateContents();
530 model->selectedRowsChanged (lastRowSelected);
531 }
532 }
533
setSelectedRows(const SparseSet<int> & setOfRowsToBeSelected,const NotificationType sendNotificationEventToModel)534 void ListBox::setSelectedRows (const SparseSet<int>& setOfRowsToBeSelected,
535 const NotificationType sendNotificationEventToModel)
536 {
537 selected = setOfRowsToBeSelected;
538 selected.removeRange ({ totalItems, std::numeric_limits<int>::max() });
539
540 if (! isRowSelected (lastRowSelected))
541 lastRowSelected = getSelectedRow (0);
542
543 viewport->updateContents();
544
545 if (model != nullptr && sendNotificationEventToModel == sendNotification)
546 model->selectedRowsChanged (lastRowSelected);
547 }
548
getSelectedRows() const549 SparseSet<int> ListBox::getSelectedRows() const
550 {
551 return selected;
552 }
553
selectRangeOfRows(int firstRow,int lastRow,bool dontScrollToShowThisRange)554 void ListBox::selectRangeOfRows (int firstRow, int lastRow, bool dontScrollToShowThisRange)
555 {
556 if (multipleSelection && (firstRow != lastRow))
557 {
558 const int numRows = totalItems - 1;
559 firstRow = jlimit (0, jmax (0, numRows), firstRow);
560 lastRow = jlimit (0, jmax (0, numRows), lastRow);
561
562 selected.addRange ({ jmin (firstRow, lastRow),
563 jmax (firstRow, lastRow) + 1 });
564
565 selected.removeRange ({ lastRow, lastRow + 1 });
566 }
567
568 selectRowInternal (lastRow, dontScrollToShowThisRange, false, true);
569 }
570
flipRowSelection(const int row)571 void ListBox::flipRowSelection (const int row)
572 {
573 if (isRowSelected (row))
574 deselectRow (row);
575 else
576 selectRowInternal (row, false, false, true);
577 }
578
deselectAllRows()579 void ListBox::deselectAllRows()
580 {
581 if (! selected.isEmpty())
582 {
583 selected.clear();
584 lastRowSelected = -1;
585
586 viewport->updateContents();
587
588 if (model != nullptr)
589 model->selectedRowsChanged (lastRowSelected);
590 }
591 }
592
selectRowsBasedOnModifierKeys(const int row,ModifierKeys mods,const bool isMouseUpEvent)593 void ListBox::selectRowsBasedOnModifierKeys (const int row,
594 ModifierKeys mods,
595 const bool isMouseUpEvent)
596 {
597 if (multipleSelection && (mods.isCommandDown() || alwaysFlipSelection))
598 {
599 flipRowSelection (row);
600 }
601 else if (multipleSelection && mods.isShiftDown() && lastRowSelected >= 0)
602 {
603 selectRangeOfRows (lastRowSelected, row);
604 }
605 else if ((! mods.isPopupMenu()) || ! isRowSelected (row))
606 {
607 selectRowInternal (row, false, ! (multipleSelection && (! isMouseUpEvent) && isRowSelected (row)), true);
608 }
609 }
610
getNumSelectedRows() const611 int ListBox::getNumSelectedRows() const
612 {
613 return selected.size();
614 }
615
getSelectedRow(const int index) const616 int ListBox::getSelectedRow (const int index) const
617 {
618 return (isPositiveAndBelow (index, selected.size()))
619 ? selected [index] : -1;
620 }
621
isRowSelected(const int row) const622 bool ListBox::isRowSelected (const int row) const
623 {
624 return selected.contains (row);
625 }
626
getLastRowSelected() const627 int ListBox::getLastRowSelected() const
628 {
629 return isRowSelected (lastRowSelected) ? lastRowSelected : -1;
630 }
631
632 //==============================================================================
getRowContainingPosition(const int x,const int y) const633 int ListBox::getRowContainingPosition (const int x, const int y) const noexcept
634 {
635 if (isPositiveAndBelow (x, getWidth()))
636 {
637 const int row = (viewport->getViewPositionY() + y - viewport->getY()) / rowHeight;
638
639 if (isPositiveAndBelow (row, totalItems))
640 return row;
641 }
642
643 return -1;
644 }
645
getInsertionIndexForPosition(const int x,const int y) const646 int ListBox::getInsertionIndexForPosition (const int x, const int y) const noexcept
647 {
648 if (isPositiveAndBelow (x, getWidth()))
649 return jlimit (0, totalItems, (viewport->getViewPositionY() + y + rowHeight / 2 - viewport->getY()) / rowHeight);
650
651 return -1;
652 }
653
getComponentForRowNumber(const int row) const654 Component* ListBox::getComponentForRowNumber (const int row) const noexcept
655 {
656 if (auto* listRowComp = viewport->getComponentForRowIfOnscreen (row))
657 return listRowComp->customComponent.get();
658
659 return nullptr;
660 }
661
getRowNumberOfComponent(Component * const rowComponent) const662 int ListBox::getRowNumberOfComponent (Component* const rowComponent) const noexcept
663 {
664 return viewport->getRowNumberOfComponent (rowComponent);
665 }
666
getRowPosition(int rowNumber,bool relativeToComponentTopLeft) const667 Rectangle<int> ListBox::getRowPosition (int rowNumber, bool relativeToComponentTopLeft) const noexcept
668 {
669 auto y = viewport->getY() + rowHeight * rowNumber;
670
671 if (relativeToComponentTopLeft)
672 y -= viewport->getViewPositionY();
673
674 return { viewport->getX(), y,
675 viewport->getViewedComponent()->getWidth(), rowHeight };
676 }
677
setVerticalPosition(const double proportion)678 void ListBox::setVerticalPosition (const double proportion)
679 {
680 auto offscreen = viewport->getViewedComponent()->getHeight() - viewport->getHeight();
681
682 viewport->setViewPosition (viewport->getViewPositionX(),
683 jmax (0, roundToInt (proportion * offscreen)));
684 }
685
getVerticalPosition() const686 double ListBox::getVerticalPosition() const
687 {
688 auto offscreen = viewport->getViewedComponent()->getHeight() - viewport->getHeight();
689
690 return offscreen > 0 ? viewport->getViewPositionY() / (double) offscreen
691 : 0;
692 }
693
getVisibleRowWidth() const694 int ListBox::getVisibleRowWidth() const noexcept
695 {
696 return viewport->getViewWidth();
697 }
698
scrollToEnsureRowIsOnscreen(const int row)699 void ListBox::scrollToEnsureRowIsOnscreen (const int row)
700 {
701 viewport->scrollToEnsureRowIsOnscreen (row, getRowHeight());
702 }
703
704 //==============================================================================
keyPressed(const KeyPress & key)705 bool ListBox::keyPressed (const KeyPress& key)
706 {
707 const int numVisibleRows = viewport->getHeight() / getRowHeight();
708
709 const bool multiple = multipleSelection
710 && lastRowSelected >= 0
711 && key.getModifiers().isShiftDown();
712
713 if (key.isKeyCode (KeyPress::upKey))
714 {
715 if (multiple)
716 selectRangeOfRows (lastRowSelected, lastRowSelected - 1);
717 else
718 selectRow (jmax (0, lastRowSelected - 1));
719 }
720 else if (key.isKeyCode (KeyPress::downKey))
721 {
722 if (multiple)
723 selectRangeOfRows (lastRowSelected, lastRowSelected + 1);
724 else
725 selectRow (jmin (totalItems - 1, jmax (0, lastRowSelected + 1)));
726 }
727 else if (key.isKeyCode (KeyPress::pageUpKey))
728 {
729 if (multiple)
730 selectRangeOfRows (lastRowSelected, lastRowSelected - numVisibleRows);
731 else
732 selectRow (jmax (0, jmax (0, lastRowSelected) - numVisibleRows));
733 }
734 else if (key.isKeyCode (KeyPress::pageDownKey))
735 {
736 if (multiple)
737 selectRangeOfRows (lastRowSelected, lastRowSelected + numVisibleRows);
738 else
739 selectRow (jmin (totalItems - 1, jmax (0, lastRowSelected) + numVisibleRows));
740 }
741 else if (key.isKeyCode (KeyPress::homeKey))
742 {
743 if (multiple)
744 selectRangeOfRows (lastRowSelected, 0);
745 else
746 selectRow (0);
747 }
748 else if (key.isKeyCode (KeyPress::endKey))
749 {
750 if (multiple)
751 selectRangeOfRows (lastRowSelected, totalItems - 1);
752 else
753 selectRow (totalItems - 1);
754 }
755 else if (key.isKeyCode (KeyPress::returnKey) && isRowSelected (lastRowSelected))
756 {
757 if (model != nullptr)
758 model->returnKeyPressed (lastRowSelected);
759 }
760 else if ((key.isKeyCode (KeyPress::deleteKey) || key.isKeyCode (KeyPress::backspaceKey))
761 && isRowSelected (lastRowSelected))
762 {
763 if (model != nullptr)
764 model->deleteKeyPressed (lastRowSelected);
765 }
766 else if (multipleSelection && key == KeyPress ('a', ModifierKeys::commandModifier, 0))
767 {
768 selectRangeOfRows (0, std::numeric_limits<int>::max());
769 }
770 else
771 {
772 return false;
773 }
774
775 return true;
776 }
777
keyStateChanged(const bool isKeyDown)778 bool ListBox::keyStateChanged (const bool isKeyDown)
779 {
780 return isKeyDown
781 && (KeyPress::isKeyCurrentlyDown (KeyPress::upKey)
782 || KeyPress::isKeyCurrentlyDown (KeyPress::pageUpKey)
783 || KeyPress::isKeyCurrentlyDown (KeyPress::downKey)
784 || KeyPress::isKeyCurrentlyDown (KeyPress::pageDownKey)
785 || KeyPress::isKeyCurrentlyDown (KeyPress::homeKey)
786 || KeyPress::isKeyCurrentlyDown (KeyPress::endKey)
787 || KeyPress::isKeyCurrentlyDown (KeyPress::returnKey));
788 }
789
mouseWheelMove(const MouseEvent & e,const MouseWheelDetails & wheel)790 void ListBox::mouseWheelMove (const MouseEvent& e, const MouseWheelDetails& wheel)
791 {
792 bool eventWasUsed = false;
793
794 if (wheel.deltaX != 0.0f && getHorizontalScrollBar().isVisible())
795 {
796 eventWasUsed = true;
797 getHorizontalScrollBar().mouseWheelMove (e, wheel);
798 }
799
800 if (wheel.deltaY != 0.0f && getVerticalScrollBar().isVisible())
801 {
802 eventWasUsed = true;
803 getVerticalScrollBar().mouseWheelMove (e, wheel);
804 }
805
806 if (! eventWasUsed)
807 Component::mouseWheelMove (e, wheel);
808 }
809
mouseUp(const MouseEvent & e)810 void ListBox::mouseUp (const MouseEvent& e)
811 {
812 if (e.mouseWasClicked() && model != nullptr)
813 model->backgroundClicked (e);
814 }
815
816 //==============================================================================
setRowHeight(const int newHeight)817 void ListBox::setRowHeight (const int newHeight)
818 {
819 rowHeight = jmax (1, newHeight);
820 viewport->setSingleStepSizes (20, rowHeight);
821 updateContent();
822 }
823
getNumRowsOnScreen() const824 int ListBox::getNumRowsOnScreen() const noexcept
825 {
826 return viewport->getMaximumVisibleHeight() / rowHeight;
827 }
828
setMinimumContentWidth(const int newMinimumWidth)829 void ListBox::setMinimumContentWidth (const int newMinimumWidth)
830 {
831 minimumRowWidth = newMinimumWidth;
832 updateContent();
833 }
834
getVisibleContentWidth() const835 int ListBox::getVisibleContentWidth() const noexcept { return viewport->getMaximumVisibleWidth(); }
836
getVerticalScrollBar() const837 ScrollBar& ListBox::getVerticalScrollBar() const noexcept { return viewport->getVerticalScrollBar(); }
getHorizontalScrollBar() const838 ScrollBar& ListBox::getHorizontalScrollBar() const noexcept { return viewport->getHorizontalScrollBar(); }
839
colourChanged()840 void ListBox::colourChanged()
841 {
842 setOpaque (findColour (backgroundColourId).isOpaque());
843 viewport->setOpaque (isOpaque());
844 repaint();
845 }
846
parentHierarchyChanged()847 void ListBox::parentHierarchyChanged()
848 {
849 colourChanged();
850 }
851
setOutlineThickness(int newThickness)852 void ListBox::setOutlineThickness (int newThickness)
853 {
854 outlineThickness = newThickness;
855 resized();
856 }
857
setHeaderComponent(std::unique_ptr<Component> newHeaderComponent)858 void ListBox::setHeaderComponent (std::unique_ptr<Component> newHeaderComponent)
859 {
860 headerComponent = std::move (newHeaderComponent);
861 addAndMakeVisible (headerComponent.get());
862 ListBox::resized();
863 }
864
repaintRow(const int rowNumber)865 void ListBox::repaintRow (const int rowNumber) noexcept
866 {
867 repaint (getRowPosition (rowNumber, true));
868 }
869
createSnapshotOfRows(const SparseSet<int> & rows,int & imageX,int & imageY)870 Image ListBox::createSnapshotOfRows (const SparseSet<int>& rows, int& imageX, int& imageY)
871 {
872 Rectangle<int> imageArea;
873 auto firstRow = getRowContainingPosition (0, viewport->getY());
874
875 for (int i = getNumRowsOnScreen() + 2; --i >= 0;)
876 {
877 if (rows.contains (firstRow + i))
878 {
879 if (auto* rowComp = viewport->getComponentForRowIfOnscreen (firstRow + i))
880 {
881 auto pos = getLocalPoint (rowComp, Point<int>());
882
883 imageArea = imageArea.getUnion ({ pos.x, pos.y, rowComp->getWidth(), rowComp->getHeight() });
884 }
885 }
886 }
887
888 imageArea = imageArea.getIntersection (getLocalBounds());
889 imageX = imageArea.getX();
890 imageY = imageArea.getY();
891
892 auto listScale = Component::getApproximateScaleFactorForComponent (this);
893 Image snapshot (Image::ARGB,
894 roundToInt ((float) imageArea.getWidth() * listScale),
895 roundToInt ((float) imageArea.getHeight() * listScale),
896 true);
897
898 for (int i = getNumRowsOnScreen() + 2; --i >= 0;)
899 {
900 if (rows.contains (firstRow + i))
901 {
902 if (auto* rowComp = viewport->getComponentForRowIfOnscreen (firstRow + i))
903 {
904 Graphics g (snapshot);
905 g.setOrigin (getLocalPoint (rowComp, Point<int>()) - imageArea.getPosition());
906
907 auto rowScale = Component::getApproximateScaleFactorForComponent (rowComp);
908
909 if (g.reduceClipRegion (rowComp->getLocalBounds() * rowScale))
910 {
911 g.beginTransparencyLayer (0.6f);
912 g.addTransform (AffineTransform::scale (rowScale));
913 rowComp->paintEntireComponent (g, false);
914 g.endTransparencyLayer();
915 }
916 }
917 }
918 }
919
920 return snapshot;
921 }
922
startDragAndDrop(const MouseEvent & e,const SparseSet<int> & rowsToDrag,const var & dragDescription,bool allowDraggingToOtherWindows)923 void ListBox::startDragAndDrop (const MouseEvent& e, const SparseSet<int>& rowsToDrag, const var& dragDescription, bool allowDraggingToOtherWindows)
924 {
925 if (auto* dragContainer = DragAndDropContainer::findParentDragContainerFor (this))
926 {
927 int x, y;
928 auto dragImage = createSnapshotOfRows (rowsToDrag, x, y);
929
930 auto p = Point<int> (x, y) - e.getEventRelativeTo (this).position.toInt();
931 dragContainer->startDragging (dragDescription, this, dragImage, allowDraggingToOtherWindows, &p, &e.source);
932 }
933 else
934 {
935 // to be able to do a drag-and-drop operation, the listbox needs to
936 // be inside a component which is also a DragAndDropContainer.
937 jassertfalse;
938 }
939 }
940
941 //==============================================================================
refreshComponentForRow(int,bool,Component * existingComponentToUpdate)942 Component* ListBoxModel::refreshComponentForRow (int, bool, Component* existingComponentToUpdate)
943 {
944 ignoreUnused (existingComponentToUpdate);
945 jassert (existingComponentToUpdate == nullptr); // indicates a failure in the code that recycles the components
946 return nullptr;
947 }
948
listBoxItemClicked(int,const MouseEvent &)949 void ListBoxModel::listBoxItemClicked (int, const MouseEvent&) {}
listBoxItemDoubleClicked(int,const MouseEvent &)950 void ListBoxModel::listBoxItemDoubleClicked (int, const MouseEvent&) {}
backgroundClicked(const MouseEvent &)951 void ListBoxModel::backgroundClicked (const MouseEvent&) {}
selectedRowsChanged(int)952 void ListBoxModel::selectedRowsChanged (int) {}
deleteKeyPressed(int)953 void ListBoxModel::deleteKeyPressed (int) {}
returnKeyPressed(int)954 void ListBoxModel::returnKeyPressed (int) {}
listWasScrolled()955 void ListBoxModel::listWasScrolled() {}
getDragSourceDescription(const SparseSet<int> &)956 var ListBoxModel::getDragSourceDescription (const SparseSet<int>&) { return {}; }
getTooltipForRow(int)957 String ListBoxModel::getTooltipForRow (int) { return {}; }
getMouseCursorForRow(int)958 MouseCursor ListBoxModel::getMouseCursorForRow (int) { return MouseCursor::NormalCursor; }
959
960 } // namespace juce
961