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