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