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