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 //==============================================================================
30 /**
31     A subclass of this is used to drive a ListBox.
32 
33     @see ListBox
34 
35     @tags{GUI}
36 */
37 class JUCE_API  ListBoxModel
38 {
39 public:
40     //==============================================================================
41     /** Destructor. */
42     virtual ~ListBoxModel() = default;
43 
44     //==============================================================================
45     /** This has to return the number of items in the list.
46         @see ListBox::getNumRows()
47     */
48     virtual int getNumRows() = 0;
49 
50     /** This method must be implemented to draw a row of the list.
51         Note that the rowNumber value may be greater than the number of rows in your
52         list, so be careful that you don't assume it's less than getNumRows().
53     */
54     virtual void paintListBoxItem (int rowNumber,
55                                    Graphics& g,
56                                    int width, int height,
57                                    bool rowIsSelected) = 0;
58 
59     /** This is used to create or update a custom component to go in a row of the list.
60 
61         Any row may contain a custom component, or can just be drawn with the paintListBoxItem() method
62         and handle mouse clicks with listBoxItemClicked().
63 
64         This method will be called whenever a custom component might need to be updated - e.g.
65         when the list is changed, or ListBox::updateContent() is called.
66 
67         If you don't need a custom component for the specified row, then return nullptr.
68         (Bear in mind that even if you're not creating a new component, you may still need to
69         delete existingComponentToUpdate if it's non-null).
70 
71         If you do want a custom component, and the existingComponentToUpdate is null, then
72         this method must create a suitable new component and return it.
73 
74         If the existingComponentToUpdate is non-null, it will be a pointer to a component previously created
75         by this method. In this case, the method must either update it to make sure it's correctly representing
76         the given row (which may be different from the one that the component was created for), or it can
77         delete this component and return a new one.
78 
79         The component that your method returns will be deleted by the ListBox when it is no longer needed.
80 
81         Bear in mind that if you put a custom component inside the row but still want the
82         listbox to automatically handle clicking, selection, etc, then you'll need to make sure
83         your custom component doesn't intercept all the mouse events that land on it, e.g by
84         using Component::setInterceptsMouseClicks().
85     */
86     virtual Component* refreshComponentForRow (int rowNumber, bool isRowSelected,
87                                                Component* existingComponentToUpdate);
88 
89     /** This can be overridden to react to the user clicking on a row.
90         @see listBoxItemDoubleClicked
91     */
92     virtual void listBoxItemClicked (int row, const MouseEvent&);
93 
94     /** This can be overridden to react to the user double-clicking on a row.
95         @see listBoxItemClicked
96     */
97     virtual void listBoxItemDoubleClicked (int row, const MouseEvent&);
98 
99     /** This can be overridden to react to the user clicking on a part of the list where
100         there are no rows.
101         @see listBoxItemClicked
102     */
103     virtual void backgroundClicked (const MouseEvent&);
104 
105     /** Override this to be informed when rows are selected or deselected.
106 
107         This will be called whenever a row is selected or deselected. If a range of
108         rows is selected all at once, this will just be called once for that event.
109 
110         @param lastRowSelected      the last row that the user selected. If no
111                                     rows are currently selected, this may be -1.
112     */
113     virtual void selectedRowsChanged (int lastRowSelected);
114 
115     /** Override this to be informed when the delete key is pressed.
116 
117         If no rows are selected when they press the key, this won't be called.
118 
119         @param lastRowSelected   the last row that had been selected when they pressed the
120                                  key - if there are multiple selections, this might not be
121                                  very useful
122     */
123     virtual void deleteKeyPressed (int lastRowSelected);
124 
125     /** Override this to be informed when the return key is pressed.
126 
127         If no rows are selected when they press the key, this won't be called.
128 
129         @param lastRowSelected   the last row that had been selected when they pressed the
130                                  key - if there are multiple selections, this might not be
131                                   very useful
132     */
133     virtual void returnKeyPressed (int lastRowSelected);
134 
135     /** Override this to be informed when the list is scrolled.
136 
137         This might be caused by the user moving the scrollbar, or by programmatic changes
138         to the list position.
139     */
140     virtual void listWasScrolled();
141 
142     /** To allow rows from your list to be dragged-and-dropped, implement this method.
143 
144         If this returns a non-null variant then when the user drags a row, the listbox will
145         try to find a DragAndDropContainer in its parent hierarchy, and will use it to trigger
146         a drag-and-drop operation, using this string as the source description, with the listbox
147         itself as the source component.
148 
149         @see DragAndDropContainer::startDragging
150     */
151     virtual var getDragSourceDescription (const SparseSet<int>& rowsToDescribe);
152 
153     /** You can override this to provide tool tips for specific rows.
154         @see TooltipClient
155     */
156     virtual String getTooltipForRow (int row);
157 
158     /** You can override this to return a custom mouse cursor for each row. */
159     virtual MouseCursor getMouseCursorForRow (int row);
160 };
161 
162 
163 //==============================================================================
164 /**
165     A list of items that can be scrolled vertically.
166 
167     To create a list, you'll need to create a subclass of ListBoxModel. This can
168     either paint each row of the list and respond to events via callbacks, or for
169     more specialised tasks, it can supply a custom component to fill each row.
170 
171     @see ComboBox, TableListBox
172 
173     @tags{GUI}
174 */
175 class JUCE_API  ListBox  : public Component,
176                            public SettableTooltipClient
177 {
178 public:
179     //==============================================================================
180     /** Creates a ListBox.
181 
182         The model pointer passed-in can be null, in which case you can set it later
183         with setModel().
184     */
185     ListBox (const String& componentName = String(),
186              ListBoxModel* model = nullptr);
187 
188     /** Destructor. */
189     ~ListBox() override;
190 
191 
192     //==============================================================================
193     /** Changes the current data model to display. */
194     void setModel (ListBoxModel* newModel);
195 
196     /** Returns the current list model. */
getModel()197     ListBoxModel* getModel() const noexcept                     { return model; }
198 
199 
200     //==============================================================================
201     /** Causes the list to refresh its content.
202 
203         Call this when the number of rows in the list changes, or if you want it
204         to call refreshComponentForRow() on all the row components.
205 
206         This must only be called from the main message thread.
207     */
208     void updateContent();
209 
210     //==============================================================================
211     /** Turns on multiple-selection of rows.
212 
213         By default this is disabled.
214 
215         When your row component gets clicked you'll need to call the
216         selectRowsBasedOnModifierKeys() method to tell the list that it's been
217         clicked and to get it to do the appropriate selection based on whether
218         the ctrl/shift keys are held down.
219     */
220     void setMultipleSelectionEnabled (bool shouldBeEnabled) noexcept;
221 
222     /** If enabled, this makes the listbox flip the selection status of
223         each row that the user clicks, without affecting other selected rows.
224 
225         (This only has an effect if multiple selection is also enabled).
226         If not enabled, you can still get the same row-flipping behaviour by holding
227         down CMD or CTRL when clicking.
228     */
229     void setClickingTogglesRowSelection (bool flipRowSelection) noexcept;
230 
231     /** Sets whether a row should be selected when the mouse is pressed or released.
232         By default this is true, but you may want to turn it off.
233     */
234     void setRowSelectedOnMouseDown (bool isSelectedOnMouseDown) noexcept;
235 
236     /** Makes the list react to mouse moves by selecting the row that the mouse if over.
237 
238         This function is here primarily for the ComboBox class to use, but might be
239         useful for some other purpose too.
240     */
241     void setMouseMoveSelectsRows (bool shouldSelect);
242 
243     //==============================================================================
244     /** Selects a row.
245 
246         If the row is already selected, this won't do anything.
247 
248         @param rowNumber                the row to select
249         @param dontScrollToShowThisRow  if true, the list's position won't change; if false and
250                                         the selected row is off-screen, it'll scroll to make
251                                         sure that row is on-screen
252         @param deselectOthersFirst      if true and there are multiple selections, these will
253                                         first be deselected before this item is selected
254         @see isRowSelected, selectRowsBasedOnModifierKeys, flipRowSelection, deselectRow,
255              deselectAllRows, selectRangeOfRows
256     */
257     void selectRow (int rowNumber,
258                     bool dontScrollToShowThisRow = false,
259                     bool deselectOthersFirst = true);
260 
261     /** Selects a set of rows.
262 
263         This will add these rows to the current selection, so you might need to
264         clear the current selection first with deselectAllRows()
265 
266         @param firstRow                       the first row to select (inclusive)
267         @param lastRow                        the last row to select (inclusive)
268         @param dontScrollToShowThisRange      if true, the list's position won't change; if false and
269                                               the selected range is off-screen, it'll scroll to make
270                                               sure that the range of rows is on-screen
271     */
272     void selectRangeOfRows (int firstRow,
273                             int lastRow,
274                             bool dontScrollToShowThisRange = false);
275 
276     /** Deselects a row.
277         If it's not currently selected, this will do nothing.
278         @see selectRow, deselectAllRows
279     */
280     void deselectRow (int rowNumber);
281 
282     /** Deselects any currently selected rows.
283         @see deselectRow
284     */
285     void deselectAllRows();
286 
287     /** Selects or deselects a row.
288         If the row's currently selected, this deselects it, and vice-versa.
289     */
290     void flipRowSelection (int rowNumber);
291 
292     /** Returns a sparse set indicating the rows that are currently selected.
293         @see setSelectedRows
294     */
295     SparseSet<int> getSelectedRows() const;
296 
297     /** Sets the rows that should be selected, based on an explicit set of ranges.
298 
299         If sendNotificationEventToModel is true, the ListBoxModel::selectedRowsChanged()
300         method will be called. If it's false, no notification will be sent to the model.
301 
302         @see getSelectedRows
303     */
304     void setSelectedRows (const SparseSet<int>& setOfRowsToBeSelected,
305                           NotificationType sendNotificationEventToModel = sendNotification);
306 
307     /** Checks whether a row is selected.
308     */
309     bool isRowSelected (int rowNumber) const;
310 
311     /** Returns the number of rows that are currently selected.
312         @see getSelectedRow, isRowSelected, getLastRowSelected
313     */
314     int getNumSelectedRows() const;
315 
316     /** Returns the row number of a selected row.
317 
318         This will return the row number of the Nth selected row. The row numbers returned will
319         be sorted in order from low to high.
320 
321         @param index    the index of the selected row to return, (from 0 to getNumSelectedRows() - 1)
322         @returns        the row number, or -1 if the index was out of range or if there aren't any rows
323                         selected
324         @see getNumSelectedRows, isRowSelected, getLastRowSelected
325     */
326     int getSelectedRow (int index = 0) const;
327 
328     /** Returns the last row that the user selected.
329 
330         This isn't the same as the highest row number that is currently selected - if the user
331         had multiply-selected rows 10, 5 and then 6 in that order, this would return 6.
332 
333         If nothing is selected, it will return -1.
334     */
335     int getLastRowSelected() const;
336 
337     /** Multiply-selects rows based on the modifier keys.
338 
339         If no modifier keys are down, this will select the given row and
340         deselect any others.
341 
342         If the ctrl (or command on the Mac) key is down, it'll flip the
343         state of the selected row.
344 
345         If the shift key is down, it'll select up to the given row from the
346         last row selected.
347 
348         @see selectRow
349     */
350     void selectRowsBasedOnModifierKeys (int rowThatWasClickedOn,
351                                         ModifierKeys modifiers,
352                                         bool isMouseUpEvent);
353 
354     //==============================================================================
355     /** Scrolls the list to a particular position.
356 
357         The proportion is between 0 and 1.0, so 0 scrolls to the top of the list,
358         1.0 scrolls to the bottom.
359 
360         If the total number of rows all fit onto the screen at once, then this
361         method won't do anything.
362 
363         @see getVerticalPosition
364     */
365     void setVerticalPosition (double newProportion);
366 
367     /** Returns the current vertical position as a proportion of the total.
368 
369         This can be used in conjunction with setVerticalPosition() to save and restore
370         the list's position. It returns a value in the range 0 to 1.
371 
372         @see setVerticalPosition
373     */
374     double getVerticalPosition() const;
375 
376     /** Scrolls if necessary to make sure that a particular row is visible. */
377     void scrollToEnsureRowIsOnscreen (int row);
378 
379     /** Returns a reference to the vertical scrollbar. */
380     ScrollBar& getVerticalScrollBar() const noexcept;
381 
382     /** Returns a reference to the horizontal scrollbar. */
383     ScrollBar& getHorizontalScrollBar() const noexcept;
384 
385     /** Finds the row index that contains a given x,y position.
386         The position is relative to the ListBox's top-left.
387         If no row exists at this position, the method will return -1.
388         @see getComponentForRowNumber
389     */
390     int getRowContainingPosition (int x, int y) const noexcept;
391 
392     /** Finds a row index that would be the most suitable place to insert a new
393         item for a given position.
394 
395         This is useful when the user is e.g. dragging and dropping onto the listbox,
396         because it lets you easily choose the best position to insert the item that
397         they drop, based on where they drop it.
398 
399         If the position is out of range, this will return -1. If the position is
400         beyond the end of the list, it will return getNumRows() to indicate the end
401         of the list.
402 
403         @see getComponentForRowNumber
404     */
405     int getInsertionIndexForPosition (int x, int y) const noexcept;
406 
407     /** Returns the position of one of the rows, relative to the top-left of
408         the listbox.
409 
410         This may be off-screen, and the range of the row number that is passed-in is
411         not checked to see if it's a valid row.
412     */
413     Rectangle<int> getRowPosition (int rowNumber,
414                                    bool relativeToComponentTopLeft) const noexcept;
415 
416     /** Finds the row component for a given row in the list.
417 
418         The component returned will have been created using ListBoxModel::refreshComponentForRow().
419 
420         If the component for this row is off-screen or if the row is out-of-range,
421         this will return nullptr.
422 
423         @see getRowContainingPosition
424     */
425     Component* getComponentForRowNumber (int rowNumber) const noexcept;
426 
427     /** Returns the row number that the given component represents.
428         If the component isn't one of the list's rows, this will return -1.
429     */
430     int getRowNumberOfComponent (Component* rowComponent) const noexcept;
431 
432     /** Returns the width of a row (which may be less than the width of this component
433         if there's a scrollbar).
434     */
435     int getVisibleRowWidth() const noexcept;
436 
437     //==============================================================================
438     /** Sets the height of each row in the list.
439         The default height is 22 pixels.
440         @see getRowHeight
441     */
442     void setRowHeight (int newHeight);
443 
444     /** Returns the height of a row in the list.
445         @see setRowHeight
446     */
getRowHeight()447     int getRowHeight() const noexcept                   { return rowHeight; }
448 
449     /** Returns the number of rows actually visible.
450 
451         This is the number of whole rows which will fit on-screen, so the value might
452         be more than the actual number of rows in the list.
453     */
454     int getNumRowsOnScreen() const noexcept;
455 
456     //==============================================================================
457     /** A set of colour IDs to use to change the colour of various aspects of the label.
458 
459         These constants can be used either via the Component::setColour(), or LookAndFeel::setColour()
460         methods.
461 
462         @see Component::setColour, Component::findColour, LookAndFeel::setColour, LookAndFeel::findColour
463     */
464     enum ColourIds
465     {
466         backgroundColourId      = 0x1002800, /**< The background colour to fill the list with.
467                                                   Make this transparent if you don't want the background to be filled. */
468         outlineColourId         = 0x1002810, /**< An optional colour to use to draw a border around the list.
469                                                   Make this transparent to not have an outline. */
470         textColourId            = 0x1002820  /**< The preferred colour to use for drawing text in the listbox. */
471     };
472 
473     /** Sets the thickness of a border that will be drawn around the box.
474 
475         To set the colour of the outline, use @code setColour (ListBox::outlineColourId, colourXYZ); @endcode
476         @see outlineColourId
477     */
478     void setOutlineThickness (int outlineThickness);
479 
480     /** Returns the thickness of outline that will be drawn around the listbox.
481         @see setOutlineColour
482     */
getOutlineThickness()483     int getOutlineThickness() const noexcept            { return outlineThickness; }
484 
485     /** Sets a component that the list should use as a header.
486 
487         This will position the given component at the top of the list, maintaining the
488         height of the component passed-in, but rescaling it horizontally to match the
489         width of the items in the listbox.
490 
491         The component will be deleted when setHeaderComponent() is called with a
492         different component, or when the listbox is deleted.
493     */
494     void setHeaderComponent (std::unique_ptr<Component> newHeaderComponent);
495 
496     /** Returns whatever header component was set with setHeaderComponent(). */
getHeaderComponent()497     Component* getHeaderComponent() const noexcept      { return headerComponent.get(); }
498 
499     /** Changes the width of the rows in the list.
500 
501         This can be used to make the list's row components wider than the list itself - the
502         width of the rows will be either the width of the list or this value, whichever is
503         greater, and if the rows become wider than the list, a horizontal scrollbar will
504         appear.
505 
506         The default value for this is 0, which means that the rows will always
507         be the same width as the list.
508     */
509     void setMinimumContentWidth (int newMinimumWidth);
510 
511     /** Returns the space currently available for the row items, taking into account
512         borders, scrollbars, etc.
513     */
514     int getVisibleContentWidth() const noexcept;
515 
516     /** Repaints one of the rows.
517 
518         This does not invoke updateContent(), it just invokes a straightforward repaint
519         for the area covered by this row.
520     */
521     void repaintRow (int rowNumber) noexcept;
522 
523     /** This fairly obscure method creates an image that shows the row components specified
524         in rows (for example, these could be the currently selected row components).
525 
526         It's a handy method for doing drag-and-drop, as it can be passed to the
527         DragAndDropContainer for use as the drag image.
528 
529         Note that it will make the row components temporarily invisible, so if you're
530         using custom components this could affect them if they're sensitive to that
531         sort of thing.
532 
533         @see Component::createComponentSnapshot
534     */
535     virtual Image createSnapshotOfRows (const SparseSet<int>& rows, int& x, int& y);
536 
537     /** Returns the viewport that this ListBox uses.
538 
539         You may need to use this to change parameters such as whether scrollbars
540         are shown, etc.
541     */
542     Viewport* getViewport() const noexcept;
543 
544     //==============================================================================
545     /** @internal */
546     bool keyPressed (const KeyPress&) override;
547     /** @internal */
548     bool keyStateChanged (bool isKeyDown) override;
549     /** @internal */
550     void paint (Graphics&) override;
551     /** @internal */
552     void paintOverChildren (Graphics&) override;
553     /** @internal */
554     void resized() override;
555     /** @internal */
556     void visibilityChanged() override;
557     /** @internal */
558     void mouseWheelMove (const MouseEvent&, const MouseWheelDetails&) override;
559     /** @internal */
560     void mouseUp (const MouseEvent&) override;
561     /** @internal */
562     void colourChanged() override;
563     /** @internal */
564     void parentHierarchyChanged() override;
565     /** @internal */
566     void startDragAndDrop (const MouseEvent&, const SparseSet<int>& rowsToDrag,
567                            const var& dragDescription, bool allowDraggingToOtherWindows);
568 
569 private:
570     //==============================================================================
571     JUCE_PUBLIC_IN_DLL_BUILD (class ListViewport)
572     JUCE_PUBLIC_IN_DLL_BUILD (class RowComponent)
573     friend class ListViewport;
574     friend class TableListBox;
575     ListBoxModel* model;
576     std::unique_ptr<ListViewport> viewport;
577     std::unique_ptr<Component> headerComponent;
578     std::unique_ptr<MouseListener> mouseMoveSelector;
579     SparseSet<int> selected;
580     int totalItems = 0, rowHeight = 22, minimumRowWidth = 0;
581     int outlineThickness = 0;
582     int lastRowSelected = -1;
583     bool multipleSelection = false, alwaysFlipSelection = false, hasDoneInitialUpdate = false, selectOnMouseDown = true;
584 
585     void selectRowInternal (int rowNumber, bool dontScrollToShowThisRow,
586                             bool deselectOthersFirst, bool isMouseClick);
587 
588    #if JUCE_CATCH_DEPRECATED_CODE_MISUSE
589     // This method's bool parameter has changed: see the new method signature.
590     JUCE_DEPRECATED (void setSelectedRows (const SparseSet<int>&, bool));
591     // This method has been replaced by the more flexible method createSnapshotOfRows.
592     // Please call createSnapshotOfRows (getSelectedRows(), x, y) to get the same behaviour.
JUCE_DEPRECATED(virtual void createSnapshotOfSelectedRows (int &,int &))593     JUCE_DEPRECATED (virtual void createSnapshotOfSelectedRows (int&, int&)) {}
594    #endif
595 
596     JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ListBox)
597 };
598 
599 } // namespace juce
600