1 /* 2 * Copyright 2001-2009, Haiku, Inc. All rights resrerved. 3 * Distributed under the terms of the MIT license. 4 * 5 * Authors: 6 * Ulrich Wimboeck 7 * Marc Flerackers (mflerackers@androme.be) 8 * Stephan Assmus <superstippi@gmx.de> 9 * Axel Dörfler, axeld@pinc-software.de 10 * Rene Gollent (rene@gollent.com) 11 */ 12 13 14 #include <ListView.h> 15 16 #include <stdio.h> 17 18 #include <Autolock.h> 19 #include <LayoutUtils.h> 20 #include <PropertyInfo.h> 21 #include <ScrollBar.h> 22 #include <ScrollView.h> 23 #include <Window.h> 24 25 #include <binary_compatibility/Interface.h> 26 27 28 struct track_data { 29 BPoint drag_start; 30 int32 item_index; 31 bool was_selected; 32 bool try_drag; 33 bigtime_t last_click_time; 34 }; 35 36 const float kDoubleClickTresh = 6; 37 38 static property_info sProperties[] = { 39 { "Item", { B_COUNT_PROPERTIES, 0 }, { B_DIRECT_SPECIFIER, 0 }, 40 "Returns the number of BListItems currently in the list.", 0, { B_INT32_TYPE } 41 }, 42 43 { "Item", { B_EXECUTE_PROPERTY, 0 }, { B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 44 B_RANGE_SPECIFIER, B_REVERSE_RANGE_SPECIFIER, 0 }, 45 "Select and invoke the specified items, first removing any existing selection." 46 }, 47 48 { "Selection", { B_COUNT_PROPERTIES, 0 }, { B_DIRECT_SPECIFIER, 0 }, 49 "Returns int32 count of items in the selection.", 0, { B_INT32_TYPE } 50 }, 51 52 { "Selection", { B_EXECUTE_PROPERTY, 0 }, { B_DIRECT_SPECIFIER, 0 }, 53 "Invoke items in selection." 54 }, 55 56 { "Selection", { B_GET_PROPERTY, 0 }, { B_DIRECT_SPECIFIER, 0 }, 57 "Returns int32 indices of all items in the selection.", 0, { B_INT32_TYPE } 58 }, 59 60 { "Selection", { B_SET_PROPERTY, 0 }, { B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 61 B_RANGE_SPECIFIER, B_REVERSE_RANGE_SPECIFIER, 0 }, 62 "Extends current selection or deselects specified items. Boolean field \"data\" " 63 "chooses selection or deselection.", 0, { B_BOOL_TYPE } 64 }, 65 66 { "Selection", { B_SET_PROPERTY, 0 }, { B_DIRECT_SPECIFIER, 0 }, 67 "Select or deselect all items in the selection. Boolean field \"data\" chooses " 68 "selection or deselection.", 0, { B_BOOL_TYPE } 69 }, 70 }; 71 72 73 BListView::BListView(BRect frame, const char* name, list_view_type type, 74 uint32 resizingMode, uint32 flags) 75 : BView(frame, name, resizingMode, flags) 76 { 77 _InitObject(type); 78 } 79 80 81 BListView::BListView(const char* name, list_view_type type, uint32 flags) 82 : BView(name, flags) 83 { 84 _InitObject(type); 85 } 86 87 88 BListView::BListView(list_view_type type) 89 : BView(NULL, B_WILL_DRAW | B_FRAME_EVENTS | B_NAVIGABLE) 90 { 91 _InitObject(type); 92 } 93 94 95 BListView::BListView(BMessage* archive) 96 : BView(archive) 97 { 98 int32 listType; 99 archive->FindInt32("_lv_type", &listType); 100 _InitObject((list_view_type)listType); 101 102 int32 i = 0; 103 BMessage subData; 104 while (archive->FindMessage("_l_items", i++, &subData) == B_OK) { 105 BArchivable *object = instantiate_object(&subData); 106 if (!object) 107 continue; 108 109 BListItem *item = dynamic_cast<BListItem*>(object); 110 if (item) 111 AddItem(item); 112 } 113 114 if (archive->HasMessage("_msg")) { 115 BMessage *invokationMessage = new BMessage; 116 117 archive->FindMessage("_msg", invokationMessage); 118 SetInvocationMessage(invokationMessage); 119 } 120 121 if (archive->HasMessage("_2nd_msg")) { 122 BMessage *selectionMessage = new BMessage; 123 124 archive->FindMessage("_2nd_msg", selectionMessage); 125 SetSelectionMessage(selectionMessage); 126 } 127 } 128 129 130 BListView::~BListView() 131 { 132 // NOTE: According to BeBook, BListView does not free the items itself. 133 delete fTrack; 134 SetSelectionMessage(NULL); 135 } 136 137 138 // #pragma mark - 139 140 141 BArchivable* 142 BListView::Instantiate(BMessage* archive) 143 { 144 if (validate_instantiation(archive, "BListView")) 145 return new BListView(archive); 146 147 return NULL; 148 } 149 150 151 status_t 152 BListView::Archive(BMessage* archive, bool deep) const 153 { 154 status_t status = BView::Archive(archive, deep); 155 if (status < B_OK) 156 return status; 157 158 status = archive->AddInt32("_lv_type", fListType); 159 if (status == B_OK && deep) { 160 BListItem *item; 161 int32 i = 0; 162 163 while ((item = ItemAt(i++))) { 164 BMessage subData; 165 status = item->Archive(&subData, true); 166 if (status >= B_OK) 167 status = archive->AddMessage("_l_items", &subData); 168 169 if (status < B_OK) 170 break; 171 } 172 } 173 174 if (status >= B_OK && InvocationMessage() != NULL) 175 status = archive->AddMessage("_msg", InvocationMessage()); 176 177 if (status == B_OK && fSelectMessage != NULL) 178 status = archive->AddMessage("_2nd_msg", fSelectMessage); 179 180 return status; 181 } 182 183 184 // #pragma mark - 185 186 187 void 188 BListView::Draw(BRect updateRect) 189 { 190 int32 count = CountItems(); 191 if (count == 0) 192 return; 193 194 BRect itemFrame(0, 0, Bounds().right, -1); 195 for (int i = 0; i < count; i++) { 196 BListItem* item = ItemAt(i); 197 itemFrame.bottom = itemFrame.top + ceilf(item->Height()) - 1; 198 199 if (itemFrame.Intersects(updateRect)) 200 DrawItem(item, itemFrame); 201 202 itemFrame.top = itemFrame.bottom + 1; 203 } 204 } 205 206 207 void 208 BListView::AttachedToWindow() 209 { 210 BView::AttachedToWindow(); 211 _FontChanged(); 212 213 if (!Messenger().IsValid()) 214 SetTarget(Window(), NULL); 215 216 _FixupScrollBar(); 217 } 218 219 220 void 221 BListView::DetachedFromWindow() 222 { 223 BView::DetachedFromWindow(); 224 } 225 226 227 void 228 BListView::AllAttached() 229 { 230 BView::AllAttached(); 231 } 232 233 234 void 235 BListView::AllDetached() 236 { 237 BView::AllDetached(); 238 } 239 240 241 void 242 BListView::FrameResized(float width, float height) 243 { 244 _FixupScrollBar(); 245 } 246 247 248 void 249 BListView::FrameMoved(BPoint new_position) 250 { 251 BView::FrameMoved(new_position); 252 } 253 254 255 void 256 BListView::TargetedByScrollView(BScrollView *view) 257 { 258 fScrollView = view; 259 // TODO: We could SetFlags(Flags() | B_FRAME_EVENTS) here, but that 260 // may mess up application code which manages this by some other means 261 // and doesn't want us to be messing with flags. 262 } 263 264 265 void 266 BListView::WindowActivated(bool state) 267 { 268 BView::WindowActivated(state); 269 } 270 271 272 // #pragma mark - 273 274 275 void 276 BListView::MessageReceived(BMessage* msg) 277 { 278 switch (msg->what) { 279 case B_COUNT_PROPERTIES: 280 case B_EXECUTE_PROPERTY: 281 case B_GET_PROPERTY: 282 case B_SET_PROPERTY: 283 { 284 BPropertyInfo propInfo(sProperties); 285 BMessage specifier; 286 const char *property; 287 288 if (msg->GetCurrentSpecifier(NULL, &specifier) != B_OK 289 || specifier.FindString("property", &property) != B_OK) 290 return; 291 292 switch (propInfo.FindMatch(msg, 0, &specifier, msg->what, 293 property)) { 294 case B_ERROR: 295 BView::MessageReceived(msg); 296 break; 297 298 case 0: 299 { 300 BMessage reply(B_REPLY); 301 reply.AddInt32("result", CountItems()); 302 reply.AddInt32("error", B_OK); 303 304 msg->SendReply(&reply); 305 break; 306 } 307 308 case 1: 309 break; 310 311 case 2: 312 { 313 int32 count = 0; 314 315 for (int32 i = 0; i < CountItems(); i++) { 316 if (ItemAt(i)->IsSelected()) 317 count++; 318 } 319 320 BMessage reply(B_REPLY); 321 reply.AddInt32("result", count); 322 reply.AddInt32("error", B_OK); 323 324 msg->SendReply(&reply); 325 break; 326 } 327 328 case 3: 329 break; 330 331 case 4: 332 { 333 BMessage reply (B_REPLY); 334 335 for (int32 i = 0; i < CountItems(); i++) { 336 if (ItemAt(i)->IsSelected()) 337 reply.AddInt32("result", i); 338 } 339 340 reply.AddInt32("error", B_OK); 341 342 msg->SendReply(&reply); 343 break; 344 } 345 346 case 5: 347 break; 348 349 case 6: 350 { 351 BMessage reply(B_REPLY); 352 353 bool select; 354 if (msg->FindBool("data", &select) == B_OK && select) 355 Select(0, CountItems() - 1, false); 356 else 357 DeselectAll(); 358 359 reply.AddInt32("error", B_OK); 360 361 msg->SendReply(&reply); 362 break; 363 } 364 } 365 break; 366 } 367 368 case B_SELECT_ALL: 369 if (fListType == B_MULTIPLE_SELECTION_LIST) 370 Select(0, CountItems() - 1, false); 371 break; 372 373 default: 374 BView::MessageReceived(msg); 375 } 376 } 377 378 379 void 380 BListView::KeyDown(const char *bytes, int32 numBytes) 381 { 382 bool extend 383 = fListType == B_MULTIPLE_SELECTION_LIST 384 && (modifiers() & B_SHIFT_KEY) != 0; 385 386 switch (bytes[0]) { 387 case B_UP_ARROW: 388 { 389 if (fFirstSelected == -1) { 390 // if nothing is selected yet, always select the first item 391 Select(0); 392 } else { 393 if (fAnchorIndex > 0) { 394 if (!extend || fAnchorIndex <= fFirstSelected) 395 Select(fAnchorIndex - 1, extend); 396 else 397 Deselect(fAnchorIndex--); 398 } 399 } 400 401 ScrollToSelection(); 402 break; 403 } 404 case B_DOWN_ARROW: 405 { 406 if (fFirstSelected == -1) { 407 // if nothing is selected yet, always select the first item 408 Select(0); 409 } else { 410 if (fAnchorIndex < CountItems() - 1) { 411 if (!extend || fAnchorIndex >= fLastSelected) 412 Select(fAnchorIndex + 1, extend); 413 else 414 Deselect(fAnchorIndex++); 415 } 416 } 417 418 ScrollToSelection(); 419 break; 420 } 421 422 case B_HOME: 423 if (extend) { 424 Select(0, fAnchorIndex, true); 425 fAnchorIndex = 0; 426 } else 427 Select(0, false); 428 ScrollToSelection(); 429 break; 430 case B_END: 431 if (extend) { 432 Select(fAnchorIndex, CountItems() - 1, true); 433 fAnchorIndex = CountItems() - 1; 434 } else 435 Select(CountItems() - 1, false); 436 ScrollToSelection(); 437 break; 438 439 case B_PAGE_UP: 440 { 441 BPoint scrollOffset(LeftTop()); 442 scrollOffset.y = max_c(0, scrollOffset.y - Bounds().Height()); 443 ScrollTo(scrollOffset); 444 break; 445 } 446 case B_PAGE_DOWN: 447 { 448 BPoint scrollOffset(LeftTop()); 449 if (BListItem* item = LastItem()) { 450 scrollOffset.y += Bounds().Height(); 451 scrollOffset.y = min_c(item->Bottom() - Bounds().Height(), 452 scrollOffset.y); 453 } 454 ScrollTo(scrollOffset); 455 break; 456 } 457 458 case B_RETURN: 459 case B_SPACE: 460 Invoke(); 461 break; 462 463 default: 464 BView::KeyDown(bytes, numBytes); 465 } 466 } 467 468 469 void 470 BListView::MouseDown(BPoint point) 471 { 472 if (!IsFocus()) { 473 MakeFocus(); 474 Sync(); 475 Window()->UpdateIfNeeded(); 476 } 477 478 BMessage *message = Looper()->CurrentMessage(); 479 int32 index = IndexOf(point); 480 481 // If the user double (or more) clicked within the current selection, 482 // we don't change the selection but invoke the selection. 483 // TODO: move this code someplace where it can be shared everywhere 484 // instead of every class having to reimplement it, once some sane 485 // API for it is decided. 486 BPoint delta = point - fTrack->drag_start; 487 bigtime_t sysTime; 488 Window()->CurrentMessage()->FindInt64("when", &sysTime); 489 bigtime_t timeDelta = sysTime - fTrack->last_click_time; 490 bigtime_t doubleClickSpeed; 491 get_click_speed(&doubleClickSpeed); 492 bool doubleClick = false; 493 494 if (timeDelta < doubleClickSpeed 495 && fabs(delta.x) < kDoubleClickTresh 496 && fabs(delta.y) < kDoubleClickTresh 497 && fTrack->item_index == index) 498 doubleClick = true; 499 500 if (doubleClick && index >= fFirstSelected && index <= fLastSelected) { 501 fTrack->drag_start.Set(LONG_MAX, LONG_MAX); 502 Invoke(); 503 return; 504 } 505 506 int32 modifiers; 507 message->FindInt32("modifiers", &modifiers); 508 509 if (!doubleClick) { 510 fTrack->drag_start = point; 511 fTrack->last_click_time = system_time(); 512 fTrack->item_index = index; 513 fTrack->was_selected = index >= 0 ? ItemAt(index)->IsSelected() : false; 514 fTrack->try_drag = true; 515 } 516 517 if (index > -1) { 518 if (fListType == B_MULTIPLE_SELECTION_LIST) { 519 if (modifiers & B_SHIFT_KEY) { 520 // select entire block 521 // TODO: maybe review if we want it like in Tracker (anchor item) 522 Select(min_c(index, fFirstSelected), max_c(index, fLastSelected)); 523 } else { 524 if (modifiers & B_COMMAND_KEY) { 525 // toggle selection state of clicked item (like in Tracker) 526 // toggle selection state of clicked item 527 if (ItemAt(index)->IsSelected()) 528 Deselect(index); 529 else 530 Select(index, true); 531 } else { 532 Select(index); 533 } 534 } 535 } else { 536 // toggle selection state of clicked item 537 if ((modifiers & B_COMMAND_KEY) && ItemAt(index)->IsSelected()) 538 Deselect(index); 539 else 540 Select(index); 541 } 542 } else { 543 if (!(modifiers & B_COMMAND_KEY)) 544 DeselectAll(); 545 } 546 } 547 548 // MouseUp 549 void 550 BListView::MouseUp(BPoint pt) 551 { 552 fTrack->try_drag = false; 553 } 554 555 556 void 557 BListView::MouseMoved(BPoint where, uint32 code, const BMessage* dragMessage) 558 { 559 if (fTrack->item_index == -1 || !fTrack->try_drag) { 560 // mouse was not clicked above any item 561 // or no mouse button pressed 562 return; 563 } 564 565 // Initiate a drag if the mouse was moved far enough 566 BPoint offset = where - fTrack->drag_start; 567 float dragDistance = sqrtf(offset.x * offset.x + offset.y * offset.y); 568 if (dragDistance >= 5.0f) { 569 fTrack->try_drag = false; 570 InitiateDrag(fTrack->drag_start, fTrack->item_index, 571 fTrack->was_selected); 572 } 573 } 574 575 576 bool 577 BListView::InitiateDrag(BPoint point, int32 index, bool wasSelected) 578 { 579 return false; 580 } 581 582 583 // #pragma mark - 584 585 586 void 587 BListView::ResizeToPreferred() 588 { 589 BView::ResizeToPreferred(); 590 } 591 592 593 void 594 BListView::GetPreferredSize(float* _width, float* _height) 595 { 596 int32 count = CountItems(); 597 598 if (count > 0) { 599 float maxWidth = 0.0; 600 for (int32 i = 0; i < count; i++) { 601 float itemWidth = ItemAt(i)->Width(); 602 if (itemWidth > maxWidth) 603 maxWidth = itemWidth; 604 } 605 606 if (_width != NULL) 607 *_width = maxWidth; 608 if (_height != NULL) 609 *_height = ItemAt(count - 1)->Bottom(); 610 } else { 611 BView::GetPreferredSize(_width, _height); 612 } 613 } 614 615 616 BSize 617 BListView::MinSize() 618 { 619 // We need a stable min size: the BView implementation uses 620 // GetPreferredSize(), which by default just returns the current size. 621 return BLayoutUtils::ComposeSize(ExplicitMinSize(), BSize(10, 10)); 622 } 623 624 625 BSize 626 BListView::MaxSize() 627 { 628 return BView::MaxSize(); 629 } 630 631 632 BSize 633 BListView::PreferredSize() 634 { 635 // We need a stable preferred size: the BView implementation uses 636 // GetPreferredSize(), which by default just returns the current size. 637 return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), BSize(100, 50)); 638 } 639 640 641 // #pragma mark - 642 643 644 void 645 BListView::MakeFocus(bool focused) 646 { 647 if (IsFocus() == focused) 648 return; 649 650 BView::MakeFocus(focused); 651 652 if (fScrollView) 653 fScrollView->SetBorderHighlighted(focused); 654 } 655 656 657 void 658 BListView::SetFont(const BFont* font, uint32 mask) 659 { 660 BView::SetFont(font, mask); 661 662 if (Window() != NULL && !Window()->InViewTransaction()) 663 _FontChanged(); 664 } 665 666 667 void 668 BListView::ScrollTo(BPoint point) 669 { 670 BView::ScrollTo(point); 671 } 672 673 674 // #pragma mark - List ops 675 676 677 bool 678 BListView::AddItem(BListItem *item, int32 index) 679 { 680 if (!fList.AddItem(item, index)) 681 return false; 682 683 if (fFirstSelected != -1 && index <= fFirstSelected) 684 fFirstSelected++; 685 686 if (fLastSelected != -1 && index <= fLastSelected) 687 fLastSelected++; 688 689 if (Window()) { 690 BFont font; 691 GetFont(&font); 692 item->SetTop((index > 0) ? ItemAt(index - 1)->Bottom() + 1.0 : 0.0); 693 694 item->Update(this, &font); 695 _RecalcItemTops(index + 1); 696 697 _FixupScrollBar(); 698 _InvalidateFrom(index); 699 } 700 701 return true; 702 } 703 704 705 bool 706 BListView::AddItem(BListItem* item) 707 { 708 if (!fList.AddItem(item)) 709 return false; 710 711 // No need to adapt selection, as this item is the last in the list 712 713 if (Window()) { 714 BFont font; 715 GetFont(&font); 716 int32 index = CountItems() - 1; 717 item->SetTop((index > 0) ? ItemAt(index - 1)->Bottom() + 1.0 : 0.0); 718 719 item->Update(this, &font); 720 721 _FixupScrollBar(); 722 InvalidateItem(CountItems() - 1); 723 } 724 725 return true; 726 } 727 728 729 bool 730 BListView::AddList(BList* list, int32 index) 731 { 732 if (!fList.AddList(list, index)) 733 return false; 734 735 int32 count = fList.CountItems(); 736 737 if (fFirstSelected != -1 && index < fFirstSelected) 738 fFirstSelected += count; 739 740 if (fLastSelected != -1 && index < fLastSelected) 741 fLastSelected += count; 742 743 if (Window()) { 744 BFont font; 745 GetFont(&font); 746 747 for (int32 i = index; i <= (index + list->CountItems() - 1); i++) { 748 ItemAt(i)->SetTop((i > 0) ? ItemAt(i - 1)->Bottom() + 1.0 : 0.0); 749 ItemAt(i)->Update(this, &font); 750 } 751 752 _RecalcItemTops(index + list->CountItems() - 1); 753 754 _FixupScrollBar(); 755 Invalidate(); // TODO 756 } 757 758 return true; 759 } 760 761 762 bool 763 BListView::AddList(BList* list) 764 { 765 return AddList(list, CountItems()); 766 } 767 768 769 BListItem* 770 BListView::RemoveItem(int32 index) 771 { 772 BListItem *item = ItemAt(index); 773 if (!item) 774 return NULL; 775 776 if (item->IsSelected()) 777 Deselect(index); 778 779 if (!fList.RemoveItem(item)) 780 return NULL; 781 782 if (fFirstSelected != -1 && index < fFirstSelected) 783 fFirstSelected--; 784 785 if (fLastSelected != -1 && index < fLastSelected) 786 fLastSelected--; 787 788 if (fAnchorIndex != -1 && index < fAnchorIndex) 789 fAnchorIndex--; 790 791 _RecalcItemTops(index); 792 793 _InvalidateFrom(index); 794 _FixupScrollBar(); 795 796 return item; 797 } 798 799 800 bool 801 BListView::RemoveItem(BListItem *item) 802 { 803 return BListView::RemoveItem(IndexOf(item)) != NULL; 804 } 805 806 807 bool 808 BListView::RemoveItems(int32 index, int32 count) 809 { 810 if (index >= fList.CountItems()) 811 index = -1; 812 813 if (index < 0) 814 return false; 815 816 if (fAnchorIndex != -1 && index < fAnchorIndex) 817 fAnchorIndex = index; 818 819 fList.RemoveItems(index, count); 820 if (index < fList.CountItems()) 821 _RecalcItemTops(index); 822 Invalidate(); 823 return true; 824 } 825 826 827 void 828 BListView::SetSelectionMessage(BMessage* message) 829 { 830 delete fSelectMessage; 831 fSelectMessage = message; 832 } 833 834 835 void 836 BListView::SetInvocationMessage(BMessage* message) 837 { 838 BInvoker::SetMessage(message); 839 } 840 841 842 BMessage* 843 BListView::InvocationMessage() const 844 { 845 return BInvoker::Message(); 846 } 847 848 849 uint32 850 BListView::InvocationCommand() const 851 { 852 return BInvoker::Command(); 853 } 854 855 856 BMessage* 857 BListView::SelectionMessage() const 858 { 859 return fSelectMessage; 860 } 861 862 863 uint32 864 BListView::SelectionCommand() const 865 { 866 if (fSelectMessage) 867 return fSelectMessage->what; 868 869 return 0; 870 } 871 872 873 void 874 BListView::SetListType(list_view_type type) 875 { 876 if (fListType == B_MULTIPLE_SELECTION_LIST && 877 type == B_SINGLE_SELECTION_LIST) 878 Select(CurrentSelection(0)); 879 880 fListType = type; 881 } 882 883 884 list_view_type 885 BListView::ListType() const 886 { 887 return fListType; 888 } 889 890 891 BListItem* 892 BListView::ItemAt(int32 index) const 893 { 894 return (BListItem*)fList.ItemAt(index); 895 } 896 897 898 int32 899 BListView::IndexOf(BListItem *item) const 900 { 901 if (Window()) { 902 if (item != NULL) { 903 int32 index = IndexOf(BPoint(0.0, item->Top())); 904 if (index >= 0 && fList.ItemAt(index) == item) 905 return index; 906 return -1; 907 } 908 } 909 return fList.IndexOf(item); 910 } 911 912 913 int32 914 BListView::IndexOf(BPoint point) const 915 { 916 int32 low = 0; 917 int32 high = fList.CountItems() - 1; 918 int32 mid = -1; 919 float frameTop = -1.0; 920 float frameBottom = 1.0; 921 // binary search the list 922 while (high >= low) { 923 mid = (low + high) / 2; 924 frameTop = ItemAt(mid)->Top(); 925 frameBottom = ItemAt(mid)->Bottom(); 926 if (point.y < frameTop) 927 high = mid - 1; 928 else if (point.y > frameBottom) 929 low = mid + 1; 930 else 931 return mid; 932 } 933 934 return -1; 935 } 936 937 938 BListItem* 939 BListView::FirstItem() const 940 { 941 return (BListItem*)fList.FirstItem(); 942 } 943 944 945 BListItem* 946 BListView::LastItem() const 947 { 948 return (BListItem*)fList.LastItem(); 949 } 950 951 952 bool 953 BListView::HasItem(BListItem *item) const 954 { 955 return IndexOf(item) != -1; 956 } 957 958 959 int32 960 BListView::CountItems() const 961 { 962 return fList.CountItems(); 963 } 964 965 966 void 967 BListView::MakeEmpty() 968 { 969 if (fList.IsEmpty()) 970 return; 971 972 _DeselectAll(-1, -1); 973 fList.MakeEmpty(); 974 975 if (Window()) { 976 _FixupScrollBar(); 977 Invalidate(); 978 } 979 } 980 981 982 bool 983 BListView::IsEmpty() const 984 { 985 return fList.IsEmpty(); 986 } 987 988 989 void 990 BListView::DoForEach(bool (*func)(BListItem*)) 991 { 992 fList.DoForEach(reinterpret_cast<bool (*)(void*)>(func)); 993 } 994 995 996 void 997 BListView::DoForEach(bool (*func)(BListItem*, void*), void* arg) 998 { 999 fList.DoForEach(reinterpret_cast<bool (*)(void*, void*)>(func), arg); 1000 } 1001 1002 1003 const BListItem** 1004 BListView::Items() const 1005 { 1006 return (const BListItem**)fList.Items(); 1007 } 1008 1009 1010 void 1011 BListView::InvalidateItem(int32 index) 1012 { 1013 Invalidate(ItemFrame(index)); 1014 } 1015 1016 1017 void 1018 BListView::ScrollToSelection() 1019 { 1020 BRect itemFrame = ItemFrame(CurrentSelection(0)); 1021 1022 if (Bounds().Contains(itemFrame)) 1023 return; 1024 1025 if (itemFrame.top < Bounds().top) 1026 ScrollTo(itemFrame.left, itemFrame.top); 1027 else 1028 ScrollTo(itemFrame.left, itemFrame.bottom - Bounds().Height()); 1029 } 1030 1031 1032 void 1033 BListView::Select(int32 index, bool extend) 1034 { 1035 if (_Select(index, extend)) { 1036 SelectionChanged(); 1037 InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED); 1038 } 1039 } 1040 1041 1042 void 1043 BListView::Select(int32 start, int32 finish, bool extend) 1044 { 1045 if (_Select(start, finish, extend)) { 1046 SelectionChanged(); 1047 InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED); 1048 } 1049 } 1050 1051 1052 bool 1053 BListView::IsItemSelected(int32 index) const 1054 { 1055 BListItem *item = ItemAt(index); 1056 if (item) 1057 return item->IsSelected(); 1058 1059 return false; 1060 } 1061 1062 1063 int32 1064 BListView::CurrentSelection(int32 index) const 1065 { 1066 if (fFirstSelected == -1) 1067 return -1; 1068 1069 if (index == 0) 1070 return fFirstSelected; 1071 1072 for (int32 i = fFirstSelected; i <= fLastSelected; i++) { 1073 if (ItemAt(i)->IsSelected()) { 1074 if (index == 0) 1075 return i; 1076 1077 index--; 1078 } 1079 } 1080 1081 return -1; 1082 } 1083 1084 1085 status_t 1086 BListView::Invoke(BMessage* message) 1087 { 1088 // Note, this is more or less a copy of BControl::Invoke() and should 1089 // stay that way (ie. changes done there should be adopted here) 1090 1091 bool notify = false; 1092 uint32 kind = InvokeKind(¬ify); 1093 1094 BMessage clone(kind); 1095 status_t err = B_BAD_VALUE; 1096 1097 if (!message && !notify) 1098 message = Message(); 1099 1100 if (!message) { 1101 if (!IsWatched()) 1102 return err; 1103 } else 1104 clone = *message; 1105 1106 clone.AddInt64("when", (int64)system_time()); 1107 clone.AddPointer("source", this); 1108 clone.AddMessenger("be:sender", BMessenger(this)); 1109 1110 if (fListType == B_SINGLE_SELECTION_LIST) 1111 clone.AddInt32("index", fFirstSelected); 1112 else { 1113 if (fFirstSelected >= 0) { 1114 for (int32 i = fFirstSelected; i <= fLastSelected; i++) { 1115 if (ItemAt(i)->IsSelected()) 1116 clone.AddInt32("index", i); 1117 } 1118 } 1119 } 1120 1121 if (message) 1122 err = BInvoker::Invoke(&clone); 1123 1124 SendNotices(kind, &clone); 1125 1126 return err; 1127 } 1128 1129 1130 void 1131 BListView::DeselectAll() 1132 { 1133 if (_DeselectAll(-1, -1)) { 1134 SelectionChanged(); 1135 InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED); 1136 } 1137 } 1138 1139 1140 void 1141 BListView::DeselectExcept(int32 exceptFrom, int32 exceptTo) 1142 { 1143 if (exceptFrom > exceptTo || exceptFrom < 0 || exceptTo < 0) 1144 return; 1145 1146 if (_DeselectAll(exceptFrom, exceptTo)) { 1147 SelectionChanged(); 1148 InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED); 1149 } 1150 } 1151 1152 1153 void 1154 BListView::Deselect(int32 index) 1155 { 1156 if (_Deselect(index)) { 1157 SelectionChanged(); 1158 InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED); 1159 } 1160 } 1161 1162 1163 void 1164 BListView::SelectionChanged() 1165 { 1166 // Hook method to be implemented by subclasses 1167 } 1168 1169 1170 void 1171 BListView::SortItems(int (*cmp)(const void *, const void *)) 1172 { 1173 if (_DeselectAll(-1, -1)) { 1174 SelectionChanged(); 1175 InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED); 1176 } 1177 1178 fList.SortItems(cmp); 1179 _RecalcItemTops(0); 1180 Invalidate(); 1181 } 1182 1183 1184 bool 1185 BListView::SwapItems(int32 a, int32 b) 1186 { 1187 MiscData data; 1188 1189 data.swap.a = a; 1190 data.swap.b = b; 1191 1192 return DoMiscellaneous(B_SWAP_OP, &data); 1193 } 1194 1195 1196 bool 1197 BListView::MoveItem(int32 from, int32 to) 1198 { 1199 MiscData data; 1200 1201 data.move.from = from; 1202 data.move.to = to; 1203 1204 return DoMiscellaneous(B_MOVE_OP, &data); 1205 } 1206 1207 1208 bool 1209 BListView::ReplaceItem(int32 index, BListItem *item) 1210 { 1211 MiscData data; 1212 1213 data.replace.index = index; 1214 data.replace.item = item; 1215 1216 return DoMiscellaneous(B_REPLACE_OP, &data); 1217 } 1218 1219 1220 BRect 1221 BListView::ItemFrame(int32 index) 1222 { 1223 BRect frame = Bounds(); 1224 if (index < 0 || index >= CountItems()) { 1225 frame.top = 0; 1226 frame.bottom = -1; 1227 } else { 1228 BListItem* item = ItemAt(index); 1229 frame.top = item->Top(); 1230 frame.bottom = item->Bottom(); 1231 } 1232 return frame; 1233 } 1234 1235 1236 // #pragma mark - 1237 1238 1239 BHandler* 1240 BListView::ResolveSpecifier(BMessage* message, int32 index, 1241 BMessage* specifier, int32 form, const char* property) 1242 { 1243 BPropertyInfo propInfo(sProperties); 1244 1245 if (propInfo.FindMatch(message, 0, specifier, form, property) < 0) { 1246 return BView::ResolveSpecifier(message, index, specifier, form, 1247 property); 1248 } 1249 1250 // TODO: msg->AddInt32("_match_code_", ); 1251 1252 return this; 1253 } 1254 1255 1256 status_t 1257 BListView::GetSupportedSuites(BMessage* data) 1258 { 1259 if (data == NULL) 1260 return B_BAD_VALUE; 1261 1262 status_t err = data->AddString("suites", "suite/vnd.Be-list-view"); 1263 1264 BPropertyInfo propertyInfo(sProperties); 1265 if (err == B_OK) 1266 err = data->AddFlat("messages", &propertyInfo); 1267 1268 if (err == B_OK) 1269 return BView::GetSupportedSuites(data); 1270 return err; 1271 } 1272 1273 1274 status_t 1275 BListView::Perform(perform_code code, void* _data) 1276 { 1277 switch (code) { 1278 case PERFORM_CODE_MIN_SIZE: 1279 ((perform_data_min_size*)_data)->return_value 1280 = BListView::MinSize(); 1281 return B_OK; 1282 case PERFORM_CODE_MAX_SIZE: 1283 ((perform_data_max_size*)_data)->return_value 1284 = BListView::MaxSize(); 1285 return B_OK; 1286 case PERFORM_CODE_PREFERRED_SIZE: 1287 ((perform_data_preferred_size*)_data)->return_value 1288 = BListView::PreferredSize(); 1289 return B_OK; 1290 case PERFORM_CODE_LAYOUT_ALIGNMENT: 1291 ((perform_data_layout_alignment*)_data)->return_value 1292 = BListView::LayoutAlignment(); 1293 return B_OK; 1294 case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH: 1295 ((perform_data_has_height_for_width*)_data)->return_value 1296 = BListView::HasHeightForWidth(); 1297 return B_OK; 1298 case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH: 1299 { 1300 perform_data_get_height_for_width* data 1301 = (perform_data_get_height_for_width*)_data; 1302 BListView::GetHeightForWidth(data->width, &data->min, &data->max, 1303 &data->preferred); 1304 return B_OK; 1305 } 1306 case PERFORM_CODE_SET_LAYOUT: 1307 { 1308 perform_data_set_layout* data = (perform_data_set_layout*)_data; 1309 BListView::SetLayout(data->layout); 1310 return B_OK; 1311 } 1312 case PERFORM_CODE_LAYOUT_INVALIDATED: 1313 { 1314 perform_data_layout_invalidated* data 1315 = (perform_data_layout_invalidated*)_data; 1316 BListView::LayoutInvalidated(data->descendants); 1317 return B_OK; 1318 } 1319 case PERFORM_CODE_DO_LAYOUT: 1320 { 1321 BListView::DoLayout(); 1322 return B_OK; 1323 } 1324 } 1325 1326 return BView::Perform(code, _data); 1327 } 1328 1329 1330 bool 1331 BListView::DoMiscellaneous(MiscCode code, MiscData* data) 1332 { 1333 if (code > B_SWAP_OP) 1334 return false; 1335 1336 switch (code) { 1337 case B_NO_OP: 1338 break; 1339 1340 case B_REPLACE_OP: 1341 return _ReplaceItem(data->replace.index, data->replace.item); 1342 1343 case B_MOVE_OP: 1344 return _MoveItem(data->move.from, data->move.to); 1345 1346 case B_SWAP_OP: 1347 return _SwapItems(data->swap.a, data->swap.b); 1348 } 1349 1350 return false; 1351 } 1352 1353 1354 // #pragma mark - 1355 1356 1357 void BListView::_ReservedListView2() {} 1358 void BListView::_ReservedListView3() {} 1359 void BListView::_ReservedListView4() {} 1360 1361 1362 BListView& 1363 BListView::operator=(const BListView& /*other*/) 1364 { 1365 return *this; 1366 } 1367 1368 1369 // #pragma mark - 1370 1371 1372 void 1373 BListView::_InitObject(list_view_type type) 1374 { 1375 fListType = type; 1376 fFirstSelected = -1; 1377 fLastSelected = -1; 1378 fAnchorIndex = -1; 1379 fSelectMessage = NULL; 1380 fScrollView = NULL; 1381 fTrack = new track_data; 1382 fTrack->try_drag = false; 1383 fTrack->item_index = -1; 1384 } 1385 1386 1387 void 1388 BListView::_FixupScrollBar() 1389 { 1390 BScrollBar* vertScroller = ScrollBar(B_VERTICAL); 1391 if (!vertScroller) 1392 return; 1393 1394 BRect bounds = Bounds(); 1395 int32 count = CountItems(); 1396 1397 float itemHeight = 0.0; 1398 1399 if (CountItems() > 0) 1400 itemHeight = ItemAt(CountItems() - 1)->Bottom(); 1401 1402 if (bounds.Height() > itemHeight) { 1403 // no scrolling 1404 vertScroller->SetRange(0.0, 0.0); 1405 vertScroller->SetValue(0.0); 1406 // also scrolls ListView to the top 1407 } else { 1408 vertScroller->SetRange(0.0, itemHeight - bounds.Height() - 1.0); 1409 vertScroller->SetProportion(bounds.Height () / itemHeight); 1410 // scroll up if there is empty room on bottom 1411 if (itemHeight < bounds.bottom) 1412 ScrollBy(0.0, bounds.bottom - itemHeight); 1413 } 1414 1415 if (count != 0) 1416 vertScroller->SetSteps(ceilf(FirstItem()->Height()), bounds.Height()); 1417 } 1418 1419 1420 void 1421 BListView::_InvalidateFrom(int32 index) 1422 { 1423 // make sure index is behind last valid index 1424 int32 count = CountItems(); 1425 if (index >= count) 1426 index = count; 1427 1428 // take the item before the wanted one, 1429 // because that might already be removed 1430 index--; 1431 BRect dirty = Bounds(); 1432 if (index >= 0) 1433 dirty.top = ItemFrame(index).bottom + 1; 1434 1435 Invalidate(dirty); 1436 } 1437 1438 1439 void 1440 BListView::_FontChanged() 1441 { 1442 BFont font; 1443 GetFont(&font); 1444 for (int32 i = 0; i < CountItems(); i++) { 1445 ItemAt(i)->SetTop((i > 0) ? ItemAt(i - 1)->Bottom() + 1.0 : 0.0); 1446 ItemAt(i)->Update(this, &font); 1447 } 1448 } 1449 1450 1451 /*! Selects the item at the specified \a index, and returns \c true in 1452 case the selection was changed because of this method. 1453 If \a extend is \c false, all previously selected items are deselected. 1454 */ 1455 bool 1456 BListView::_Select(int32 index, bool extend) 1457 { 1458 if (index < 0 || index >= CountItems()) 1459 return false; 1460 1461 // only lock the window when there is one 1462 BAutolock locker(Window()); 1463 if (Window() != NULL && !locker.IsLocked()) 1464 return false; 1465 1466 bool changed = false; 1467 1468 if (!extend && fFirstSelected != -1) 1469 changed = _DeselectAll(index, index); 1470 1471 fAnchorIndex = index; 1472 1473 BListItem* item = ItemAt(index); 1474 if (!item->IsEnabled() || item->IsSelected()) { 1475 // if the item is already selected, or can't be selected, 1476 // we're done here 1477 return changed; 1478 } 1479 1480 // keep track of first and last selected item 1481 if (fFirstSelected == -1) { 1482 // no previous selection 1483 fFirstSelected = index; 1484 fLastSelected = index; 1485 } else if (index < fFirstSelected) { 1486 fFirstSelected = index; 1487 } else if (index > fLastSelected) { 1488 fLastSelected = index; 1489 } 1490 1491 ItemAt(index)->Select(); 1492 if (Window()) 1493 InvalidateItem(index); 1494 1495 return true; 1496 } 1497 1498 1499 /*! Selects the items between \a from and \a to, and returns \c true in 1500 case the selection was changed because of this method. 1501 If \a extend is \c false, all previously selected items are deselected. 1502 */ 1503 bool 1504 BListView::_Select(int32 from, int32 to, bool extend) 1505 { 1506 if (to < from) 1507 return false; 1508 1509 BAutolock locker(Window()); 1510 if (Window() && !locker.IsLocked()) 1511 return false; 1512 1513 bool changed = false; 1514 1515 if (fFirstSelected != -1 && !extend) 1516 changed = _DeselectAll(from, to); 1517 1518 if (fFirstSelected == -1) { 1519 fFirstSelected = from; 1520 fLastSelected = to; 1521 } else { 1522 if (from < fFirstSelected) 1523 fFirstSelected = from; 1524 if (to > fLastSelected) 1525 fLastSelected = to; 1526 } 1527 1528 for (int32 i = from; i <= to; ++i) { 1529 BListItem *item = ItemAt(i); 1530 if (item && !item->IsSelected()) { 1531 item->Select(); 1532 if (Window()) 1533 InvalidateItem(i); 1534 changed = true; 1535 } 1536 } 1537 1538 return changed; 1539 } 1540 1541 1542 bool 1543 BListView::_Deselect(int32 index) 1544 { 1545 if (index < 0 || index >= CountItems()) 1546 return false; 1547 1548 BWindow *window = Window(); 1549 BAutolock locker(window); 1550 if (window && !locker.IsLocked()) 1551 return false; 1552 1553 BListItem *item = ItemAt(index); 1554 1555 if (item && item->IsSelected()) { 1556 BRect frame(ItemFrame(index)); 1557 BRect bounds(Bounds()); 1558 1559 item->Deselect(); 1560 1561 if (fFirstSelected == index && fLastSelected == index) { 1562 fFirstSelected = -1; 1563 fLastSelected = -1; 1564 } else { 1565 if (fFirstSelected == index) 1566 fFirstSelected = _CalcFirstSelected(index); 1567 1568 if (fLastSelected == index) 1569 fLastSelected = _CalcLastSelected(index); 1570 } 1571 1572 if (window && bounds.Intersects(frame)) 1573 DrawItem(ItemAt(index), frame, true); 1574 } 1575 1576 return true; 1577 } 1578 1579 1580 bool 1581 BListView::_DeselectAll(int32 exceptFrom, int32 exceptTo) 1582 { 1583 if (fFirstSelected == -1) 1584 return false; 1585 1586 BAutolock locker(Window()); 1587 if (Window() && !locker.IsLocked()) 1588 return false; 1589 1590 bool changed = false; 1591 1592 for (int32 index = fFirstSelected; index <= fLastSelected; index++) { 1593 // don't deselect the items we shouldn't deselect 1594 if (exceptFrom != -1 && exceptFrom <= index && exceptTo >= index) 1595 continue; 1596 1597 BListItem *item = ItemAt(index); 1598 if (item && item->IsSelected()) { 1599 item->Deselect(); 1600 InvalidateItem(index); 1601 changed = true; 1602 } 1603 } 1604 1605 if (!changed) 1606 return false; 1607 1608 if (exceptFrom != -1) { 1609 fFirstSelected = _CalcFirstSelected(exceptFrom); 1610 fLastSelected = _CalcLastSelected(exceptTo); 1611 } else 1612 fFirstSelected = fLastSelected = -1; 1613 1614 return true; 1615 } 1616 1617 1618 int32 1619 BListView::_CalcFirstSelected(int32 after) 1620 { 1621 if (after >= CountItems()) 1622 return -1; 1623 1624 int32 count = CountItems(); 1625 for (int32 i = after; i < count; i++) { 1626 if (ItemAt(i)->IsSelected()) 1627 return i; 1628 } 1629 1630 return -1; 1631 } 1632 1633 1634 int32 1635 BListView::_CalcLastSelected(int32 before) 1636 { 1637 if (before < 0) 1638 return -1; 1639 1640 before = min_c(CountItems() - 1, before); 1641 1642 for (int32 i = before; i >= 0; i--) { 1643 if (ItemAt(i)->IsSelected()) 1644 return i; 1645 } 1646 1647 return -1; 1648 } 1649 1650 1651 void 1652 BListView::DrawItem(BListItem* item, BRect itemRect, bool complete) 1653 { 1654 item->DrawItem(this, itemRect, complete); 1655 } 1656 1657 1658 bool 1659 BListView::_SwapItems(int32 a, int32 b) 1660 { 1661 // remember frames of items before anyhing happens, 1662 // the tricky situation is when the two items have 1663 // a different height 1664 BRect aFrame = ItemFrame(a); 1665 BRect bFrame = ItemFrame(b); 1666 1667 if (!fList.SwapItems(a, b)) 1668 return false; 1669 1670 if (a == b) { 1671 // nothing to do, but success nevertheless 1672 return true; 1673 } 1674 1675 // track anchor item 1676 if (fAnchorIndex == a) 1677 fAnchorIndex = b; 1678 else if (fAnchorIndex == b) 1679 fAnchorIndex = a; 1680 1681 // track selection 1682 // NOTE: this is only important if the selection status 1683 // of both items is not the same 1684 int32 first = min_c(a, b); 1685 int32 last = max_c(a, b); 1686 if (ItemAt(a)->IsSelected() != ItemAt(b)->IsSelected()) { 1687 if (first < fFirstSelected || last > fLastSelected) 1688 _RescanSelection(min_c(first, fFirstSelected), max_c(last, fLastSelected)); 1689 // though the actually selected items stayed the 1690 // same, the selection has still changed 1691 SelectionChanged(); 1692 } 1693 1694 ItemAt(a)->SetTop(aFrame.top); 1695 ItemAt(b)->SetTop(bFrame.top); 1696 1697 // take care of invalidation 1698 if (Window()) { 1699 // NOTE: window looper is assumed to be locked! 1700 if (aFrame.Height() != bFrame.Height()) { 1701 _RecalcItemTops(first, last); 1702 // items in between shifted visually 1703 Invalidate(aFrame | bFrame); 1704 } else { 1705 Invalidate(aFrame); 1706 Invalidate(bFrame); 1707 } 1708 } 1709 1710 return true; 1711 } 1712 1713 1714 bool 1715 BListView::_MoveItem(int32 from, int32 to) 1716 { 1717 // remember item frames before doing anything 1718 BRect frameFrom = ItemFrame(from); 1719 BRect frameTo = ItemFrame(to); 1720 1721 if (!fList.MoveItem(from, to)) 1722 return false; 1723 1724 // track anchor item 1725 if (fAnchorIndex == from) 1726 fAnchorIndex = to; 1727 1728 // track selection 1729 if (ItemAt(to)->IsSelected()) { 1730 _RescanSelection(from, to); 1731 // though the actually selected items stayed the 1732 // same, the selection has still changed 1733 SelectionChanged(); 1734 } 1735 1736 _RecalcItemTops((to > from) ? from : to); 1737 1738 // take care of invalidation 1739 if (Window()) { 1740 // NOTE: window looper is assumed to be locked! 1741 Invalidate(frameFrom | frameTo); 1742 } 1743 1744 return true; 1745 } 1746 1747 1748 bool 1749 BListView::_ReplaceItem(int32 index, BListItem *item) 1750 { 1751 if (!item) 1752 return false; 1753 1754 BListItem* old = ItemAt(index); 1755 if (!old) 1756 return false; 1757 1758 BRect frame = ItemFrame(index); 1759 1760 bool selectionChanged = old->IsSelected() != item->IsSelected(); 1761 1762 // replace item 1763 if (!fList.ReplaceItem(index, item)) 1764 return false; 1765 1766 // tack selection 1767 if (selectionChanged) { 1768 int32 start = min_c(fFirstSelected, index); 1769 int32 end = max_c(fLastSelected, index); 1770 _RescanSelection(start, end); 1771 SelectionChanged(); 1772 } 1773 _RecalcItemTops(index); 1774 1775 bool itemHeightChanged = frame != ItemFrame(index); 1776 1777 // take care of invalidation 1778 if (Window()) { 1779 // NOTE: window looper is assumed to be locked! 1780 if (itemHeightChanged) 1781 _InvalidateFrom(index); 1782 else 1783 Invalidate(frame); 1784 } 1785 1786 if (itemHeightChanged) 1787 _FixupScrollBar(); 1788 1789 return true; 1790 } 1791 1792 1793 void 1794 BListView::_RescanSelection(int32 from, int32 to) 1795 { 1796 if (from > to) { 1797 int32 tmp = from; 1798 from = to; 1799 to = tmp; 1800 } 1801 1802 from = max_c(0, from); 1803 to = min_c(to, CountItems() - 1); 1804 1805 if (fAnchorIndex != -1) { 1806 if (fAnchorIndex == from) 1807 fAnchorIndex = to; 1808 else if (fAnchorIndex == to) 1809 fAnchorIndex = from; 1810 } 1811 1812 for (int32 i = from; i <= to; i++) { 1813 if (ItemAt(i)->IsSelected()) { 1814 fFirstSelected = i; 1815 break; 1816 } 1817 } 1818 1819 if (fFirstSelected > from) 1820 from = fFirstSelected; 1821 1822 fLastSelected = fFirstSelected; 1823 for (int32 i = from; i <= to; i++) { 1824 if (ItemAt(i)->IsSelected()) 1825 fLastSelected = i; 1826 } 1827 } 1828 1829 1830 void 1831 BListView::_RecalcItemTops(int32 start, int32 end) 1832 { 1833 int32 count = CountItems(); 1834 if ((start < 0) || (start >= count)) 1835 return; 1836 1837 if (end >= 0) 1838 count = end + 1; 1839 1840 float top = (start == 0) ? 0.0 : ItemAt(start - 1)->Bottom() + 1.0; 1841 1842 for (int32 i = start; i < count; i++) { 1843 BListItem *item = ItemAt(i); 1844 item->SetTop(top); 1845 top += ceilf(item->Height()); 1846 } 1847 } 1848 1849