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