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