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