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