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