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(INT32_MAX, INT32_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 = list->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 + count - 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 + count - 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 SetViewColor(ui_color(B_LIST_BACKGROUND_COLOR)); 1386 SetLowColor(ui_color(B_LIST_BACKGROUND_COLOR)); 1387 } 1388 1389 1390 void 1391 BListView::_FixupScrollBar() 1392 { 1393 BScrollBar* vertScroller = ScrollBar(B_VERTICAL); 1394 if (!vertScroller) 1395 return; 1396 1397 BRect bounds = Bounds(); 1398 int32 count = CountItems(); 1399 1400 float itemHeight = 0.0; 1401 1402 if (CountItems() > 0) 1403 itemHeight = ItemAt(CountItems() - 1)->Bottom(); 1404 1405 if (bounds.Height() > itemHeight) { 1406 // no scrolling 1407 vertScroller->SetRange(0.0, 0.0); 1408 vertScroller->SetValue(0.0); 1409 // also scrolls ListView to the top 1410 } else { 1411 vertScroller->SetRange(0.0, itemHeight - bounds.Height() - 1.0); 1412 vertScroller->SetProportion(bounds.Height () / itemHeight); 1413 // scroll up if there is empty room on bottom 1414 if (itemHeight < bounds.bottom) 1415 ScrollBy(0.0, bounds.bottom - itemHeight); 1416 } 1417 1418 if (count != 0) 1419 vertScroller->SetSteps(ceilf(FirstItem()->Height()), bounds.Height()); 1420 } 1421 1422 1423 void 1424 BListView::_InvalidateFrom(int32 index) 1425 { 1426 // make sure index is behind last valid index 1427 int32 count = CountItems(); 1428 if (index >= count) 1429 index = count; 1430 1431 // take the item before the wanted one, 1432 // because that might already be removed 1433 index--; 1434 BRect dirty = Bounds(); 1435 if (index >= 0) 1436 dirty.top = ItemFrame(index).bottom + 1; 1437 1438 Invalidate(dirty); 1439 } 1440 1441 1442 void 1443 BListView::_FontChanged() 1444 { 1445 BFont font; 1446 GetFont(&font); 1447 for (int32 i = 0; i < CountItems(); i++) { 1448 ItemAt(i)->SetTop((i > 0) ? ItemAt(i - 1)->Bottom() + 1.0 : 0.0); 1449 ItemAt(i)->Update(this, &font); 1450 } 1451 } 1452 1453 1454 /*! Selects the item at the specified \a index, and returns \c true in 1455 case the selection was changed because of this method. 1456 If \a extend is \c false, all previously selected items are deselected. 1457 */ 1458 bool 1459 BListView::_Select(int32 index, bool extend) 1460 { 1461 if (index < 0 || index >= CountItems()) 1462 return false; 1463 1464 // only lock the window when there is one 1465 BAutolock locker(Window()); 1466 if (Window() != NULL && !locker.IsLocked()) 1467 return false; 1468 1469 bool changed = false; 1470 1471 if (!extend && fFirstSelected != -1) 1472 changed = _DeselectAll(index, index); 1473 1474 fAnchorIndex = index; 1475 1476 BListItem* item = ItemAt(index); 1477 if (!item->IsEnabled() || item->IsSelected()) { 1478 // if the item is already selected, or can't be selected, 1479 // we're done here 1480 return changed; 1481 } 1482 1483 // keep track of first and last selected item 1484 if (fFirstSelected == -1) { 1485 // no previous selection 1486 fFirstSelected = index; 1487 fLastSelected = index; 1488 } else if (index < fFirstSelected) { 1489 fFirstSelected = index; 1490 } else if (index > fLastSelected) { 1491 fLastSelected = index; 1492 } 1493 1494 ItemAt(index)->Select(); 1495 if (Window()) 1496 InvalidateItem(index); 1497 1498 return true; 1499 } 1500 1501 1502 /*! Selects the items between \a from and \a to, and returns \c true in 1503 case the selection was changed because of this method. 1504 If \a extend is \c false, all previously selected items are deselected. 1505 */ 1506 bool 1507 BListView::_Select(int32 from, int32 to, bool extend) 1508 { 1509 if (to < from) 1510 return false; 1511 1512 BAutolock locker(Window()); 1513 if (Window() && !locker.IsLocked()) 1514 return false; 1515 1516 bool changed = false; 1517 1518 if (fFirstSelected != -1 && !extend) 1519 changed = _DeselectAll(from, to); 1520 1521 if (fFirstSelected == -1) { 1522 fFirstSelected = from; 1523 fLastSelected = to; 1524 } else { 1525 if (from < fFirstSelected) 1526 fFirstSelected = from; 1527 if (to > fLastSelected) 1528 fLastSelected = to; 1529 } 1530 1531 for (int32 i = from; i <= to; ++i) { 1532 BListItem *item = ItemAt(i); 1533 if (item && !item->IsSelected()) { 1534 item->Select(); 1535 if (Window()) 1536 InvalidateItem(i); 1537 changed = true; 1538 } 1539 } 1540 1541 return changed; 1542 } 1543 1544 1545 bool 1546 BListView::_Deselect(int32 index) 1547 { 1548 if (index < 0 || index >= CountItems()) 1549 return false; 1550 1551 BWindow *window = Window(); 1552 BAutolock locker(window); 1553 if (window && !locker.IsLocked()) 1554 return false; 1555 1556 BListItem *item = ItemAt(index); 1557 1558 if (item && item->IsSelected()) { 1559 BRect frame(ItemFrame(index)); 1560 BRect bounds(Bounds()); 1561 1562 item->Deselect(); 1563 1564 if (fFirstSelected == index && fLastSelected == index) { 1565 fFirstSelected = -1; 1566 fLastSelected = -1; 1567 } else { 1568 if (fFirstSelected == index) 1569 fFirstSelected = _CalcFirstSelected(index); 1570 1571 if (fLastSelected == index) 1572 fLastSelected = _CalcLastSelected(index); 1573 } 1574 1575 if (window && bounds.Intersects(frame)) 1576 DrawItem(ItemAt(index), frame, true); 1577 } 1578 1579 return true; 1580 } 1581 1582 1583 bool 1584 BListView::_DeselectAll(int32 exceptFrom, int32 exceptTo) 1585 { 1586 if (fFirstSelected == -1) 1587 return false; 1588 1589 BAutolock locker(Window()); 1590 if (Window() && !locker.IsLocked()) 1591 return false; 1592 1593 bool changed = false; 1594 1595 for (int32 index = fFirstSelected; index <= fLastSelected; index++) { 1596 // don't deselect the items we shouldn't deselect 1597 if (exceptFrom != -1 && exceptFrom <= index && exceptTo >= index) 1598 continue; 1599 1600 BListItem *item = ItemAt(index); 1601 if (item && item->IsSelected()) { 1602 item->Deselect(); 1603 InvalidateItem(index); 1604 changed = true; 1605 } 1606 } 1607 1608 if (!changed) 1609 return false; 1610 1611 if (exceptFrom != -1) { 1612 fFirstSelected = _CalcFirstSelected(exceptFrom); 1613 fLastSelected = _CalcLastSelected(exceptTo); 1614 } else 1615 fFirstSelected = fLastSelected = -1; 1616 1617 return true; 1618 } 1619 1620 1621 int32 1622 BListView::_CalcFirstSelected(int32 after) 1623 { 1624 if (after >= CountItems()) 1625 return -1; 1626 1627 int32 count = CountItems(); 1628 for (int32 i = after; i < count; i++) { 1629 if (ItemAt(i)->IsSelected()) 1630 return i; 1631 } 1632 1633 return -1; 1634 } 1635 1636 1637 int32 1638 BListView::_CalcLastSelected(int32 before) 1639 { 1640 if (before < 0) 1641 return -1; 1642 1643 before = min_c(CountItems() - 1, before); 1644 1645 for (int32 i = before; i >= 0; i--) { 1646 if (ItemAt(i)->IsSelected()) 1647 return i; 1648 } 1649 1650 return -1; 1651 } 1652 1653 1654 void 1655 BListView::DrawItem(BListItem* item, BRect itemRect, bool complete) 1656 { 1657 item->DrawItem(this, itemRect, complete); 1658 } 1659 1660 1661 bool 1662 BListView::_SwapItems(int32 a, int32 b) 1663 { 1664 // remember frames of items before anyhing happens, 1665 // the tricky situation is when the two items have 1666 // a different height 1667 BRect aFrame = ItemFrame(a); 1668 BRect bFrame = ItemFrame(b); 1669 1670 if (!fList.SwapItems(a, b)) 1671 return false; 1672 1673 if (a == b) { 1674 // nothing to do, but success nevertheless 1675 return true; 1676 } 1677 1678 // track anchor item 1679 if (fAnchorIndex == a) 1680 fAnchorIndex = b; 1681 else if (fAnchorIndex == b) 1682 fAnchorIndex = a; 1683 1684 // track selection 1685 // NOTE: this is only important if the selection status 1686 // of both items is not the same 1687 int32 first = min_c(a, b); 1688 int32 last = max_c(a, b); 1689 if (ItemAt(a)->IsSelected() != ItemAt(b)->IsSelected()) { 1690 if (first < fFirstSelected || last > fLastSelected) 1691 _RescanSelection(min_c(first, fFirstSelected), max_c(last, fLastSelected)); 1692 // though the actually selected items stayed the 1693 // same, the selection has still changed 1694 SelectionChanged(); 1695 } 1696 1697 ItemAt(a)->SetTop(aFrame.top); 1698 ItemAt(b)->SetTop(bFrame.top); 1699 1700 // take care of invalidation 1701 if (Window()) { 1702 // NOTE: window looper is assumed to be locked! 1703 if (aFrame.Height() != bFrame.Height()) { 1704 _RecalcItemTops(first, last); 1705 // items in between shifted visually 1706 Invalidate(aFrame | bFrame); 1707 } else { 1708 Invalidate(aFrame); 1709 Invalidate(bFrame); 1710 } 1711 } 1712 1713 return true; 1714 } 1715 1716 1717 bool 1718 BListView::_MoveItem(int32 from, int32 to) 1719 { 1720 // remember item frames before doing anything 1721 BRect frameFrom = ItemFrame(from); 1722 BRect frameTo = ItemFrame(to); 1723 1724 if (!fList.MoveItem(from, to)) 1725 return false; 1726 1727 // track anchor item 1728 if (fAnchorIndex == from) 1729 fAnchorIndex = to; 1730 1731 // track selection 1732 if (ItemAt(to)->IsSelected()) { 1733 _RescanSelection(from, to); 1734 // though the actually selected items stayed the 1735 // same, the selection has still changed 1736 SelectionChanged(); 1737 } 1738 1739 _RecalcItemTops((to > from) ? from : to); 1740 1741 // take care of invalidation 1742 if (Window()) { 1743 // NOTE: window looper is assumed to be locked! 1744 Invalidate(frameFrom | frameTo); 1745 } 1746 1747 return true; 1748 } 1749 1750 1751 bool 1752 BListView::_ReplaceItem(int32 index, BListItem *item) 1753 { 1754 if (!item) 1755 return false; 1756 1757 BListItem* old = ItemAt(index); 1758 if (!old) 1759 return false; 1760 1761 BRect frame = ItemFrame(index); 1762 1763 bool selectionChanged = old->IsSelected() != item->IsSelected(); 1764 1765 // replace item 1766 if (!fList.ReplaceItem(index, item)) 1767 return false; 1768 1769 // tack selection 1770 if (selectionChanged) { 1771 int32 start = min_c(fFirstSelected, index); 1772 int32 end = max_c(fLastSelected, index); 1773 _RescanSelection(start, end); 1774 SelectionChanged(); 1775 } 1776 _RecalcItemTops(index); 1777 1778 bool itemHeightChanged = frame != ItemFrame(index); 1779 1780 // take care of invalidation 1781 if (Window()) { 1782 // NOTE: window looper is assumed to be locked! 1783 if (itemHeightChanged) 1784 _InvalidateFrom(index); 1785 else 1786 Invalidate(frame); 1787 } 1788 1789 if (itemHeightChanged) 1790 _FixupScrollBar(); 1791 1792 return true; 1793 } 1794 1795 1796 void 1797 BListView::_RescanSelection(int32 from, int32 to) 1798 { 1799 if (from > to) { 1800 int32 tmp = from; 1801 from = to; 1802 to = tmp; 1803 } 1804 1805 from = max_c(0, from); 1806 to = min_c(to, CountItems() - 1); 1807 1808 if (fAnchorIndex != -1) { 1809 if (fAnchorIndex == from) 1810 fAnchorIndex = to; 1811 else if (fAnchorIndex == to) 1812 fAnchorIndex = from; 1813 } 1814 1815 for (int32 i = from; i <= to; i++) { 1816 if (ItemAt(i)->IsSelected()) { 1817 fFirstSelected = i; 1818 break; 1819 } 1820 } 1821 1822 if (fFirstSelected > from) 1823 from = fFirstSelected; 1824 1825 fLastSelected = fFirstSelected; 1826 for (int32 i = from; i <= to; i++) { 1827 if (ItemAt(i)->IsSelected()) 1828 fLastSelected = i; 1829 } 1830 } 1831 1832 1833 void 1834 BListView::_RecalcItemTops(int32 start, int32 end) 1835 { 1836 int32 count = CountItems(); 1837 if ((start < 0) || (start >= count)) 1838 return; 1839 1840 if (end >= 0) 1841 count = end + 1; 1842 1843 float top = (start == 0) ? 0.0 : ItemAt(start - 1)->Bottom() + 1.0; 1844 1845 for (int32 i = start; i < count; i++) { 1846 BListItem *item = ItemAt(i); 1847 item->SetTop(top); 1848 top += ceilf(item->Height()); 1849 } 1850 } 1851 1852