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 float scrollPos = itemFrame.top < Bounds().top ? 1041 itemFrame.top : itemFrame.bottom - Bounds().Height(); 1042 1043 if (itemFrame.top - scrollPos < Bounds().top) 1044 scrollPos = itemFrame.top; 1045 1046 ScrollTo(itemFrame.left, scrollPos); 1047 } 1048 1049 1050 void 1051 BListView::Select(int32 index, bool extend) 1052 { 1053 if (_Select(index, extend)) { 1054 SelectionChanged(); 1055 InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED); 1056 } 1057 } 1058 1059 1060 void 1061 BListView::Select(int32 start, int32 finish, bool extend) 1062 { 1063 if (_Select(start, finish, extend)) { 1064 SelectionChanged(); 1065 InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED); 1066 } 1067 } 1068 1069 1070 bool 1071 BListView::IsItemSelected(int32 index) const 1072 { 1073 BListItem* item = ItemAt(index); 1074 if (item != NULL) 1075 return item->IsSelected(); 1076 1077 return false; 1078 } 1079 1080 1081 int32 1082 BListView::CurrentSelection(int32 index) const 1083 { 1084 if (fFirstSelected == -1) 1085 return -1; 1086 1087 if (index == 0) 1088 return fFirstSelected; 1089 1090 for (int32 i = fFirstSelected; i <= fLastSelected; i++) { 1091 if (ItemAt(i)->IsSelected()) { 1092 if (index == 0) 1093 return i; 1094 1095 index--; 1096 } 1097 } 1098 1099 return -1; 1100 } 1101 1102 1103 status_t 1104 BListView::Invoke(BMessage* message) 1105 { 1106 // Note, this is more or less a copy of BControl::Invoke() and should 1107 // stay that way (ie. changes done there should be adopted here) 1108 1109 bool notify = false; 1110 uint32 kind = InvokeKind(¬ify); 1111 1112 BMessage clone(kind); 1113 status_t err = B_BAD_VALUE; 1114 1115 if (!message && !notify) 1116 message = Message(); 1117 1118 if (!message) { 1119 if (!IsWatched()) 1120 return err; 1121 } else 1122 clone = *message; 1123 1124 clone.AddInt64("when", (int64)system_time()); 1125 clone.AddPointer("source", this); 1126 clone.AddMessenger("be:sender", BMessenger(this)); 1127 1128 if (fListType == B_SINGLE_SELECTION_LIST) 1129 clone.AddInt32("index", fFirstSelected); 1130 else { 1131 if (fFirstSelected >= 0) { 1132 for (int32 i = fFirstSelected; i <= fLastSelected; i++) { 1133 if (ItemAt(i)->IsSelected()) 1134 clone.AddInt32("index", i); 1135 } 1136 } 1137 } 1138 1139 if (message) 1140 err = BInvoker::Invoke(&clone); 1141 1142 SendNotices(kind, &clone); 1143 1144 return err; 1145 } 1146 1147 1148 void 1149 BListView::DeselectAll() 1150 { 1151 if (_DeselectAll(-1, -1)) { 1152 SelectionChanged(); 1153 InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED); 1154 } 1155 } 1156 1157 1158 void 1159 BListView::DeselectExcept(int32 exceptFrom, int32 exceptTo) 1160 { 1161 if (exceptFrom > exceptTo || exceptFrom < 0 || exceptTo < 0) 1162 return; 1163 1164 if (_DeselectAll(exceptFrom, exceptTo)) { 1165 SelectionChanged(); 1166 InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED); 1167 } 1168 } 1169 1170 1171 void 1172 BListView::Deselect(int32 index) 1173 { 1174 if (_Deselect(index)) { 1175 SelectionChanged(); 1176 InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED); 1177 } 1178 } 1179 1180 1181 void 1182 BListView::SelectionChanged() 1183 { 1184 // Hook method to be implemented by subclasses 1185 } 1186 1187 1188 void 1189 BListView::SortItems(int (*cmp)(const void *, const void *)) 1190 { 1191 if (_DeselectAll(-1, -1)) { 1192 SelectionChanged(); 1193 InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED); 1194 } 1195 1196 fList.SortItems(cmp); 1197 _RecalcItemTops(0); 1198 Invalidate(); 1199 } 1200 1201 1202 bool 1203 BListView::SwapItems(int32 a, int32 b) 1204 { 1205 MiscData data; 1206 1207 data.swap.a = a; 1208 data.swap.b = b; 1209 1210 return DoMiscellaneous(B_SWAP_OP, &data); 1211 } 1212 1213 1214 bool 1215 BListView::MoveItem(int32 from, int32 to) 1216 { 1217 MiscData data; 1218 1219 data.move.from = from; 1220 data.move.to = to; 1221 1222 return DoMiscellaneous(B_MOVE_OP, &data); 1223 } 1224 1225 1226 bool 1227 BListView::ReplaceItem(int32 index, BListItem* item) 1228 { 1229 MiscData data; 1230 1231 data.replace.index = index; 1232 data.replace.item = item; 1233 1234 return DoMiscellaneous(B_REPLACE_OP, &data); 1235 } 1236 1237 1238 BRect 1239 BListView::ItemFrame(int32 index) 1240 { 1241 BRect frame = Bounds(); 1242 if (index < 0 || index >= CountItems()) { 1243 frame.top = 0; 1244 frame.bottom = -1; 1245 } else { 1246 BListItem* item = ItemAt(index); 1247 frame.top = item->Top(); 1248 frame.bottom = item->Bottom(); 1249 } 1250 return frame; 1251 } 1252 1253 1254 // #pragma mark - 1255 1256 1257 BHandler* 1258 BListView::ResolveSpecifier(BMessage* message, int32 index, 1259 BMessage* specifier, int32 form, const char* property) 1260 { 1261 BPropertyInfo propInfo(sProperties); 1262 1263 if (propInfo.FindMatch(message, 0, specifier, form, property) < 0) { 1264 return BView::ResolveSpecifier(message, index, specifier, form, 1265 property); 1266 } 1267 1268 // TODO: msg->AddInt32("_match_code_", ); 1269 1270 return this; 1271 } 1272 1273 1274 status_t 1275 BListView::GetSupportedSuites(BMessage* data) 1276 { 1277 if (data == NULL) 1278 return B_BAD_VALUE; 1279 1280 status_t err = data->AddString("suites", "suite/vnd.Be-list-view"); 1281 1282 BPropertyInfo propertyInfo(sProperties); 1283 if (err == B_OK) 1284 err = data->AddFlat("messages", &propertyInfo); 1285 1286 if (err == B_OK) 1287 return BView::GetSupportedSuites(data); 1288 return err; 1289 } 1290 1291 1292 status_t 1293 BListView::Perform(perform_code code, void* _data) 1294 { 1295 switch (code) { 1296 case PERFORM_CODE_MIN_SIZE: 1297 ((perform_data_min_size*)_data)->return_value 1298 = BListView::MinSize(); 1299 return B_OK; 1300 case PERFORM_CODE_MAX_SIZE: 1301 ((perform_data_max_size*)_data)->return_value 1302 = BListView::MaxSize(); 1303 return B_OK; 1304 case PERFORM_CODE_PREFERRED_SIZE: 1305 ((perform_data_preferred_size*)_data)->return_value 1306 = BListView::PreferredSize(); 1307 return B_OK; 1308 case PERFORM_CODE_LAYOUT_ALIGNMENT: 1309 ((perform_data_layout_alignment*)_data)->return_value 1310 = BListView::LayoutAlignment(); 1311 return B_OK; 1312 case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH: 1313 ((perform_data_has_height_for_width*)_data)->return_value 1314 = BListView::HasHeightForWidth(); 1315 return B_OK; 1316 case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH: 1317 { 1318 perform_data_get_height_for_width* data 1319 = (perform_data_get_height_for_width*)_data; 1320 BListView::GetHeightForWidth(data->width, &data->min, &data->max, 1321 &data->preferred); 1322 return B_OK; 1323 } 1324 case PERFORM_CODE_SET_LAYOUT: 1325 { 1326 perform_data_set_layout* data = (perform_data_set_layout*)_data; 1327 BListView::SetLayout(data->layout); 1328 return B_OK; 1329 } 1330 case PERFORM_CODE_LAYOUT_INVALIDATED: 1331 { 1332 perform_data_layout_invalidated* data 1333 = (perform_data_layout_invalidated*)_data; 1334 BListView::LayoutInvalidated(data->descendants); 1335 return B_OK; 1336 } 1337 case PERFORM_CODE_DO_LAYOUT: 1338 { 1339 BListView::DoLayout(); 1340 return B_OK; 1341 } 1342 } 1343 1344 return BView::Perform(code, _data); 1345 } 1346 1347 1348 bool 1349 BListView::DoMiscellaneous(MiscCode code, MiscData* data) 1350 { 1351 if (code > B_SWAP_OP) 1352 return false; 1353 1354 switch (code) { 1355 case B_NO_OP: 1356 break; 1357 1358 case B_REPLACE_OP: 1359 return _ReplaceItem(data->replace.index, data->replace.item); 1360 1361 case B_MOVE_OP: 1362 return _MoveItem(data->move.from, data->move.to); 1363 1364 case B_SWAP_OP: 1365 return _SwapItems(data->swap.a, data->swap.b); 1366 } 1367 1368 return false; 1369 } 1370 1371 1372 // #pragma mark - 1373 1374 1375 void BListView::_ReservedListView2() {} 1376 void BListView::_ReservedListView3() {} 1377 void BListView::_ReservedListView4() {} 1378 1379 1380 BListView& 1381 BListView::operator=(const BListView& /*other*/) 1382 { 1383 return *this; 1384 } 1385 1386 1387 // #pragma mark - 1388 1389 1390 void 1391 BListView::_InitObject(list_view_type type) 1392 { 1393 fListType = type; 1394 fFirstSelected = -1; 1395 fLastSelected = -1; 1396 fAnchorIndex = -1; 1397 fSelectMessage = NULL; 1398 fScrollView = NULL; 1399 fTrack = new track_data; 1400 fTrack->try_drag = false; 1401 fTrack->item_index = -1; 1402 1403 SetViewColor(ui_color(B_LIST_BACKGROUND_COLOR)); 1404 SetLowColor(ui_color(B_LIST_BACKGROUND_COLOR)); 1405 } 1406 1407 1408 void 1409 BListView::_FixupScrollBar() 1410 { 1411 BScrollBar* vertScroller = ScrollBar(B_VERTICAL); 1412 if (!vertScroller) 1413 return; 1414 1415 BRect bounds = Bounds(); 1416 int32 count = CountItems(); 1417 1418 float itemHeight = 0.0; 1419 1420 if (CountItems() > 0) 1421 itemHeight = ItemAt(CountItems() - 1)->Bottom(); 1422 1423 if (bounds.Height() > itemHeight) { 1424 // no scrolling 1425 vertScroller->SetRange(0.0, 0.0); 1426 vertScroller->SetValue(0.0); 1427 // also scrolls ListView to the top 1428 } else { 1429 vertScroller->SetRange(0.0, itemHeight - bounds.Height() - 1.0); 1430 vertScroller->SetProportion(bounds.Height () / itemHeight); 1431 // scroll up if there is empty room on bottom 1432 if (itemHeight < bounds.bottom) 1433 ScrollBy(0.0, bounds.bottom - itemHeight); 1434 } 1435 1436 if (count != 0) 1437 vertScroller->SetSteps(ceilf(FirstItem()->Height()), bounds.Height()); 1438 } 1439 1440 1441 void 1442 BListView::_InvalidateFrom(int32 index) 1443 { 1444 // make sure index is behind last valid index 1445 int32 count = CountItems(); 1446 if (index >= count) 1447 index = count; 1448 1449 // take the item before the wanted one, 1450 // because that might already be removed 1451 index--; 1452 BRect dirty = Bounds(); 1453 if (index >= 0) 1454 dirty.top = ItemFrame(index).bottom + 1; 1455 1456 Invalidate(dirty); 1457 } 1458 1459 1460 void 1461 BListView::_FontChanged() 1462 { 1463 BFont font; 1464 GetFont(&font); 1465 for (int32 i = 0; i < CountItems(); i++) { 1466 ItemAt(i)->SetTop((i > 0) ? ItemAt(i - 1)->Bottom() + 1.0 : 0.0); 1467 ItemAt(i)->Update(this, &font); 1468 } 1469 } 1470 1471 1472 /*! Selects the item at the specified \a index, and returns \c true in 1473 case the selection was changed because of this method. 1474 If \a extend is \c false, all previously selected items are deselected. 1475 */ 1476 bool 1477 BListView::_Select(int32 index, bool extend) 1478 { 1479 if (index < 0 || index >= CountItems()) 1480 return false; 1481 1482 // only lock the window when there is one 1483 BAutolock locker(Window()); 1484 if (Window() != NULL && !locker.IsLocked()) 1485 return false; 1486 1487 bool changed = false; 1488 1489 if (!extend && fFirstSelected != -1) 1490 changed = _DeselectAll(index, index); 1491 1492 fAnchorIndex = index; 1493 1494 BListItem* item = ItemAt(index); 1495 if (!item->IsEnabled() || item->IsSelected()) { 1496 // if the item is already selected, or can't be selected, 1497 // we're done here 1498 return changed; 1499 } 1500 1501 // keep track of first and last selected item 1502 if (fFirstSelected == -1) { 1503 // no previous selection 1504 fFirstSelected = index; 1505 fLastSelected = index; 1506 } else if (index < fFirstSelected) { 1507 fFirstSelected = index; 1508 } else if (index > fLastSelected) { 1509 fLastSelected = index; 1510 } 1511 1512 ItemAt(index)->Select(); 1513 if (Window()) 1514 InvalidateItem(index); 1515 1516 return true; 1517 } 1518 1519 1520 /*! 1521 Selects the items between \a from and \a to, and returns \c true in 1522 case the selection was changed because of this method. 1523 If \a extend is \c false, all previously selected items are deselected. 1524 */ 1525 bool 1526 BListView::_Select(int32 from, int32 to, bool extend) 1527 { 1528 if (to < from) 1529 return false; 1530 1531 BAutolock locker(Window()); 1532 if (Window() && !locker.IsLocked()) 1533 return false; 1534 1535 bool changed = false; 1536 1537 if (fFirstSelected != -1 && !extend) 1538 changed = _DeselectAll(from, to); 1539 1540 if (fFirstSelected == -1) { 1541 fFirstSelected = from; 1542 fLastSelected = to; 1543 } else { 1544 if (from < fFirstSelected) 1545 fFirstSelected = from; 1546 if (to > fLastSelected) 1547 fLastSelected = to; 1548 } 1549 1550 for (int32 i = from; i <= to; ++i) { 1551 BListItem *item = ItemAt(i); 1552 if (item && !item->IsSelected()) { 1553 item->Select(); 1554 if (Window()) 1555 InvalidateItem(i); 1556 changed = true; 1557 } 1558 } 1559 1560 return changed; 1561 } 1562 1563 1564 bool 1565 BListView::_Deselect(int32 index) 1566 { 1567 if (index < 0 || index >= CountItems()) 1568 return false; 1569 1570 BWindow *window = Window(); 1571 BAutolock locker(window); 1572 if (window && !locker.IsLocked()) 1573 return false; 1574 1575 BListItem *item = ItemAt(index); 1576 1577 if (item && item->IsSelected()) { 1578 BRect frame(ItemFrame(index)); 1579 BRect bounds(Bounds()); 1580 1581 item->Deselect(); 1582 1583 if (fFirstSelected == index && fLastSelected == index) { 1584 fFirstSelected = -1; 1585 fLastSelected = -1; 1586 } else { 1587 if (fFirstSelected == index) 1588 fFirstSelected = _CalcFirstSelected(index); 1589 1590 if (fLastSelected == index) 1591 fLastSelected = _CalcLastSelected(index); 1592 } 1593 1594 if (window && bounds.Intersects(frame)) 1595 DrawItem(ItemAt(index), frame, true); 1596 } 1597 1598 return true; 1599 } 1600 1601 1602 bool 1603 BListView::_DeselectAll(int32 exceptFrom, int32 exceptTo) 1604 { 1605 if (fFirstSelected == -1) 1606 return false; 1607 1608 BAutolock locker(Window()); 1609 if (Window() && !locker.IsLocked()) 1610 return false; 1611 1612 bool changed = false; 1613 1614 for (int32 index = fFirstSelected; index <= fLastSelected; index++) { 1615 // don't deselect the items we shouldn't deselect 1616 if (exceptFrom != -1 && exceptFrom <= index && exceptTo >= index) 1617 continue; 1618 1619 BListItem *item = ItemAt(index); 1620 if (item && item->IsSelected()) { 1621 item->Deselect(); 1622 InvalidateItem(index); 1623 changed = true; 1624 } 1625 } 1626 1627 if (!changed) 1628 return false; 1629 1630 if (exceptFrom != -1) { 1631 fFirstSelected = _CalcFirstSelected(exceptFrom); 1632 fLastSelected = _CalcLastSelected(exceptTo); 1633 } else 1634 fFirstSelected = fLastSelected = -1; 1635 1636 return true; 1637 } 1638 1639 1640 int32 1641 BListView::_CalcFirstSelected(int32 after) 1642 { 1643 if (after >= CountItems()) 1644 return -1; 1645 1646 int32 count = CountItems(); 1647 for (int32 i = after; i < count; i++) { 1648 if (ItemAt(i)->IsSelected()) 1649 return i; 1650 } 1651 1652 return -1; 1653 } 1654 1655 1656 int32 1657 BListView::_CalcLastSelected(int32 before) 1658 { 1659 if (before < 0) 1660 return -1; 1661 1662 before = min_c(CountItems() - 1, before); 1663 1664 for (int32 i = before; i >= 0; i--) { 1665 if (ItemAt(i)->IsSelected()) 1666 return i; 1667 } 1668 1669 return -1; 1670 } 1671 1672 1673 void 1674 BListView::DrawItem(BListItem* item, BRect itemRect, bool complete) 1675 { 1676 item->DrawItem(this, itemRect, complete); 1677 } 1678 1679 1680 bool 1681 BListView::_SwapItems(int32 a, int32 b) 1682 { 1683 // remember frames of items before anyhing happens, 1684 // the tricky situation is when the two items have 1685 // a different height 1686 BRect aFrame = ItemFrame(a); 1687 BRect bFrame = ItemFrame(b); 1688 1689 if (!fList.SwapItems(a, b)) 1690 return false; 1691 1692 if (a == b) { 1693 // nothing to do, but success nevertheless 1694 return true; 1695 } 1696 1697 // track anchor item 1698 if (fAnchorIndex == a) 1699 fAnchorIndex = b; 1700 else if (fAnchorIndex == b) 1701 fAnchorIndex = a; 1702 1703 // track selection 1704 // NOTE: this is only important if the selection status 1705 // of both items is not the same 1706 int32 first = min_c(a, b); 1707 int32 last = max_c(a, b); 1708 if (ItemAt(a)->IsSelected() != ItemAt(b)->IsSelected()) { 1709 if (first < fFirstSelected || last > fLastSelected) { 1710 _RescanSelection(min_c(first, fFirstSelected), 1711 max_c(last, fLastSelected)); 1712 } 1713 // though the actually selected items stayed the 1714 // same, the selection has still changed 1715 SelectionChanged(); 1716 } 1717 1718 ItemAt(a)->SetTop(aFrame.top); 1719 ItemAt(b)->SetTop(bFrame.top); 1720 1721 // take care of invalidation 1722 if (Window()) { 1723 // NOTE: window looper is assumed to be locked! 1724 if (aFrame.Height() != bFrame.Height()) { 1725 _RecalcItemTops(first, last); 1726 // items in between shifted visually 1727 Invalidate(aFrame | bFrame); 1728 } else { 1729 Invalidate(aFrame); 1730 Invalidate(bFrame); 1731 } 1732 } 1733 1734 return true; 1735 } 1736 1737 1738 bool 1739 BListView::_MoveItem(int32 from, int32 to) 1740 { 1741 // remember item frames before doing anything 1742 BRect frameFrom = ItemFrame(from); 1743 BRect frameTo = ItemFrame(to); 1744 1745 if (!fList.MoveItem(from, to)) 1746 return false; 1747 1748 // track anchor item 1749 if (fAnchorIndex == from) 1750 fAnchorIndex = to; 1751 1752 // track selection 1753 if (ItemAt(to)->IsSelected()) { 1754 _RescanSelection(from, to); 1755 // though the actually selected items stayed the 1756 // same, the selection has still changed 1757 SelectionChanged(); 1758 } 1759 1760 _RecalcItemTops((to > from) ? from : to); 1761 1762 // take care of invalidation 1763 if (Window()) { 1764 // NOTE: window looper is assumed to be locked! 1765 Invalidate(frameFrom | frameTo); 1766 } 1767 1768 return true; 1769 } 1770 1771 1772 bool 1773 BListView::_ReplaceItem(int32 index, BListItem* item) 1774 { 1775 if (item == NULL) 1776 return false; 1777 1778 BListItem* old = ItemAt(index); 1779 if (!old) 1780 return false; 1781 1782 BRect frame = ItemFrame(index); 1783 1784 bool selectionChanged = old->IsSelected() != item->IsSelected(); 1785 1786 // replace item 1787 if (!fList.ReplaceItem(index, item)) 1788 return false; 1789 1790 // tack selection 1791 if (selectionChanged) { 1792 int32 start = min_c(fFirstSelected, index); 1793 int32 end = max_c(fLastSelected, index); 1794 _RescanSelection(start, end); 1795 SelectionChanged(); 1796 } 1797 _RecalcItemTops(index); 1798 1799 bool itemHeightChanged = frame != ItemFrame(index); 1800 1801 // take care of invalidation 1802 if (Window()) { 1803 // NOTE: window looper is assumed to be locked! 1804 if (itemHeightChanged) 1805 _InvalidateFrom(index); 1806 else 1807 Invalidate(frame); 1808 } 1809 1810 if (itemHeightChanged) 1811 _FixupScrollBar(); 1812 1813 return true; 1814 } 1815 1816 1817 void 1818 BListView::_RescanSelection(int32 from, int32 to) 1819 { 1820 if (from > to) { 1821 int32 tmp = from; 1822 from = to; 1823 to = tmp; 1824 } 1825 1826 from = max_c(0, from); 1827 to = min_c(to, CountItems() - 1); 1828 1829 if (fAnchorIndex != -1) { 1830 if (fAnchorIndex == from) 1831 fAnchorIndex = to; 1832 else if (fAnchorIndex == to) 1833 fAnchorIndex = from; 1834 } 1835 1836 for (int32 i = from; i <= to; i++) { 1837 if (ItemAt(i)->IsSelected()) { 1838 fFirstSelected = i; 1839 break; 1840 } 1841 } 1842 1843 if (fFirstSelected > from) 1844 from = fFirstSelected; 1845 1846 fLastSelected = fFirstSelected; 1847 for (int32 i = from; i <= to; i++) { 1848 if (ItemAt(i)->IsSelected()) 1849 fLastSelected = i; 1850 } 1851 } 1852 1853 1854 void 1855 BListView::_RecalcItemTops(int32 start, int32 end) 1856 { 1857 int32 count = CountItems(); 1858 if ((start < 0) || (start >= count)) 1859 return; 1860 1861 if (end >= 0) 1862 count = end + 1; 1863 1864 float top = (start == 0) ? 0.0 : ItemAt(start - 1)->Bottom() + 1.0; 1865 1866 for (int32 i = start; i < count; i++) { 1867 BListItem *item = ItemAt(i); 1868 item->SetTop(top); 1869 top += ceilf(item->Height()); 1870 } 1871 } 1872