1 /* 2 * Copyright (c) 2001-2007, 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 if (fList.IsEmpty()) 741 return; 742 743 _DeselectAll(-1, -1); 744 fList.MakeEmpty(); 745 746 if (Window()) { 747 _FixupScrollBar(); 748 Invalidate(); 749 } 750 } 751 752 // IsEmpty 753 bool 754 BListView::IsEmpty() const 755 { 756 return fList.IsEmpty(); 757 } 758 759 // DoForEach 760 void 761 BListView::DoForEach(bool (*func)(BListItem*)) 762 { 763 fList.DoForEach(reinterpret_cast<bool (*)(void*)>(func)); 764 } 765 766 // DoForEach 767 void 768 BListView::DoForEach(bool (*func)(BListItem*, void*), void* arg ) 769 { 770 fList.DoForEach(reinterpret_cast<bool (*)(void*, void*)>(func), arg); 771 } 772 773 // Items 774 const BListItem** 775 BListView::Items() const 776 { 777 return (const BListItem**)fList.Items(); 778 } 779 780 // InvalidateItem 781 void 782 BListView::InvalidateItem(int32 index) 783 { 784 Invalidate(ItemFrame(index)); 785 } 786 787 // ScrollToSelection 788 void 789 BListView::ScrollToSelection() 790 { 791 BRect itemFrame = ItemFrame(CurrentSelection(0)); 792 793 if (Bounds().Intersects(itemFrame.InsetByCopy(0.0f, 2.0f))) 794 return; 795 796 if (itemFrame.top < Bounds().top) 797 ScrollTo(0, itemFrame.top); 798 else 799 ScrollTo(0, itemFrame.bottom - Bounds().Height()); 800 } 801 802 803 void 804 BListView::Select(int32 index, bool extend) 805 { 806 if (_Select(index, extend)) { 807 SelectionChanged(); 808 InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED); 809 } 810 } 811 812 813 void 814 BListView::Select(int32 start, int32 finish, bool extend) 815 { 816 if (_Select(start, finish, extend)) { 817 SelectionChanged(); 818 InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED); 819 } 820 } 821 822 823 bool 824 BListView::IsItemSelected(int32 index) const 825 { 826 BListItem *item = ItemAt(index); 827 if (item) 828 return item->IsSelected(); 829 830 return false; 831 } 832 833 // CurrentSelection 834 int32 835 BListView::CurrentSelection(int32 index) const 836 { 837 if (fFirstSelected == -1) 838 return -1; 839 840 if (index == 0) 841 return fFirstSelected; 842 843 for (int32 i = fFirstSelected; i <= fLastSelected; i++) { 844 if (ItemAt(i)->IsSelected()) { 845 if (index == 0) 846 return i; 847 848 index--; 849 } 850 } 851 852 return -1; 853 } 854 855 // Invoke 856 status_t 857 BListView::Invoke(BMessage *message) 858 { 859 // Note, this is more or less a copy of BControl::Invoke() and should 860 // stay that way (ie. changes done there should be adopted here) 861 862 bool notify = false; 863 uint32 kind = InvokeKind(¬ify); 864 865 BMessage clone(kind); 866 status_t err = B_BAD_VALUE; 867 868 if (!message && !notify) 869 message = Message(); 870 871 if (!message) { 872 if (!IsWatched()) 873 return err; 874 } else 875 clone = *message; 876 877 clone.AddInt64("when", (int64)system_time()); 878 clone.AddPointer("source", this); 879 clone.AddMessenger("be:sender", BMessenger(this)); 880 881 if (fListType == B_SINGLE_SELECTION_LIST) 882 clone.AddInt32("index", fFirstSelected); 883 else { 884 if (fFirstSelected >= 0) { 885 for (int32 i = fFirstSelected; i <= fLastSelected; i++) { 886 if (ItemAt(i)->IsSelected()) 887 clone.AddInt32("index", i); 888 } 889 } 890 } 891 892 if (message) 893 err = BInvoker::Invoke(&clone); 894 895 SendNotices(kind, &clone); 896 897 return err; 898 } 899 900 901 void 902 BListView::DeselectAll() 903 { 904 if (_DeselectAll(-1, -1)) { 905 SelectionChanged(); 906 InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED); 907 } 908 } 909 910 911 void 912 BListView::DeselectExcept(int32 exceptFrom, int32 exceptTo) 913 { 914 if (exceptFrom > exceptTo || exceptFrom < 0 || exceptTo < 0) 915 return; 916 917 if (_DeselectAll(exceptFrom, exceptTo)) { 918 SelectionChanged(); 919 InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED); 920 } 921 } 922 923 924 void 925 BListView::Deselect(int32 index) 926 { 927 if (_Deselect(index)) { 928 SelectionChanged(); 929 InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED); 930 } 931 } 932 933 934 void 935 BListView::SelectionChanged() 936 { 937 // Hook method to be implemented by subclasses 938 } 939 940 941 void 942 BListView::SortItems(int (*cmp)(const void *, const void *)) 943 { 944 if (_DeselectAll(-1, -1)) { 945 SelectionChanged(); 946 InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED); 947 } 948 949 fList.SortItems(cmp); 950 Invalidate(); 951 } 952 953 // SwapItems 954 bool 955 BListView::SwapItems(int32 a, int32 b) 956 { 957 MiscData data; 958 959 data.swap.a = a; 960 data.swap.b = b; 961 962 return DoMiscellaneous(B_SWAP_OP, &data); 963 } 964 965 // MoveItem 966 bool 967 BListView::MoveItem(int32 from, int32 to) 968 { 969 MiscData data; 970 971 data.move.from = from; 972 data.move.to = to; 973 974 return DoMiscellaneous(B_MOVE_OP, &data); 975 } 976 977 // ReplaceItem 978 bool 979 BListView::ReplaceItem(int32 index, BListItem *item) 980 { 981 MiscData data; 982 983 data.replace.index = index; 984 data.replace.item = item; 985 986 return DoMiscellaneous(B_REPLACE_OP, &data); 987 } 988 989 // AttachedToWindow 990 void 991 BListView::AttachedToWindow() 992 { 993 BView::AttachedToWindow(); 994 _FontChanged(); 995 996 if (!Messenger().IsValid()) 997 SetTarget(Window(), NULL); 998 999 _FixupScrollBar(); 1000 } 1001 1002 // FrameMoved 1003 void 1004 BListView::FrameMoved(BPoint new_position) 1005 { 1006 BView::FrameMoved(new_position); 1007 } 1008 1009 // ItemFrame 1010 BRect 1011 BListView::ItemFrame(int32 index) 1012 { 1013 BRect frame(0, 0, Bounds().Width(), -1); 1014 1015 if (index < 0 || index >= CountItems()) 1016 return frame; 1017 1018 // TODO: this is very expensive, the (last) offsets could be cached 1019 for (int32 i = 0; i <= index; i++) { 1020 frame.top = frame.bottom + 1; 1021 frame.bottom += (float)ceil(ItemAt(i)->Height()); 1022 } 1023 1024 return frame; 1025 } 1026 1027 1028 BHandler* 1029 BListView::ResolveSpecifier(BMessage* message, int32 index, 1030 BMessage* specifier, int32 form, const char* property) 1031 { 1032 BPropertyInfo propInfo(sProperties); 1033 1034 if (propInfo.FindMatch(message, 0, specifier, form, property) < 0) 1035 return BView::ResolveSpecifier(message, index, specifier, form, property); 1036 1037 // TODO: msg->AddInt32("_match_code_", ); 1038 1039 return this; 1040 } 1041 1042 1043 status_t 1044 BListView::GetSupportedSuites(BMessage* data) 1045 { 1046 if (data == NULL) 1047 return B_BAD_VALUE; 1048 1049 status_t err = data->AddString("suites", "suite/vnd.Be-list-view"); 1050 1051 BPropertyInfo propertyInfo(sProperties); 1052 if (err == B_OK) 1053 err = data->AddFlat("messages", &propertyInfo); 1054 1055 if (err == B_OK) 1056 return BView::GetSupportedSuites(data); 1057 return err; 1058 } 1059 1060 // Perform 1061 status_t 1062 BListView::Perform(perform_code d, void *arg) 1063 { 1064 return BView::Perform(d, arg); 1065 } 1066 1067 // WindowActivated 1068 void 1069 BListView::WindowActivated(bool state) 1070 { 1071 BView::WindowActivated(state); 1072 } 1073 1074 // DetachedFromWindow 1075 void 1076 BListView::DetachedFromWindow() 1077 { 1078 BView::DetachedFromWindow(); 1079 } 1080 1081 // InitiateDrag 1082 bool 1083 BListView::InitiateDrag(BPoint point, int32 index, bool wasSelected) 1084 { 1085 return false; 1086 } 1087 1088 // ResizeToPreferred 1089 void 1090 BListView::ResizeToPreferred() 1091 { 1092 BView::ResizeToPreferred(); 1093 } 1094 1095 // GetPreferredSize 1096 void 1097 BListView::GetPreferredSize(float *width, float *height) 1098 { 1099 BView::GetPreferredSize(width, height); 1100 } 1101 1102 // AllAttached 1103 void 1104 BListView::AllAttached() 1105 { 1106 BView::AllAttached(); 1107 } 1108 1109 // AllDetached 1110 void 1111 BListView::AllDetached() 1112 { 1113 BView::AllDetached(); 1114 } 1115 1116 BSize 1117 BListView::MinSize() 1118 { 1119 // We need a stable min size: the BView implementation uses 1120 // GetPreferredSize(), which by default just returns the current size. 1121 return BLayoutUtils::ComposeSize(ExplicitMinSize(), BSize(10, 10)); 1122 } 1123 1124 1125 BSize 1126 BListView::MaxSize() 1127 { 1128 return BView::MaxSize(); 1129 } 1130 1131 1132 BSize 1133 BListView::PreferredSize() 1134 { 1135 // We need a stable preferred size: the BView implementation uses 1136 // GetPreferredSize(), which by default just returns the current size. 1137 return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), BSize(100, 50)); 1138 } 1139 1140 1141 // DoMiscellaneous 1142 bool 1143 BListView::DoMiscellaneous(MiscCode code, MiscData *data) 1144 { 1145 if (code > B_SWAP_OP) 1146 return false; 1147 1148 switch (code) { 1149 case B_NO_OP: 1150 break; 1151 1152 case B_REPLACE_OP: 1153 return _ReplaceItem(data->replace.index, data->replace.item); 1154 1155 case B_MOVE_OP: 1156 return _MoveItem(data->move.from, data->move.to); 1157 1158 case B_SWAP_OP: 1159 return _SwapItems(data->swap.a, data->swap.b); 1160 } 1161 1162 return false; 1163 } 1164 1165 void BListView::_ReservedListView2() {} 1166 void BListView::_ReservedListView3() {} 1167 void BListView::_ReservedListView4() {} 1168 1169 BListView &BListView::operator=(const BListView &) 1170 { 1171 return *this; 1172 } 1173 1174 // _InitObject 1175 void 1176 BListView::_InitObject(list_view_type type) 1177 { 1178 fListType = type; 1179 fFirstSelected = -1; 1180 fLastSelected = -1; 1181 fAnchorIndex = -1; 1182 fWidth = Bounds().Width(); 1183 fSelectMessage = NULL; 1184 fScrollView = NULL; 1185 fTrack = new track_data; 1186 fTrack->try_drag = false; 1187 fTrack->item_index = -1; 1188 } 1189 1190 // _FixupScrollBar 1191 void 1192 BListView::_FixupScrollBar() 1193 { 1194 BScrollBar* vertScroller = ScrollBar(B_VERTICAL); 1195 1196 if (!vertScroller) 1197 return; 1198 1199 BRect bounds = Bounds(); 1200 int32 count = CountItems(); 1201 1202 float itemHeight = 0; 1203 for (int32 i = 0; BListItem* item = ItemAt(i); i++) { 1204 itemHeight += item->Height(); 1205 } 1206 1207 if (bounds.Height() > itemHeight) { 1208 // no scrolling 1209 vertScroller->SetRange(0.0, 0.0); 1210 vertScroller->SetValue(0.0); 1211 // also scrolls ListView to the top 1212 } else { 1213 vertScroller->SetRange(0.0, itemHeight - bounds.Height() - 1.0); 1214 vertScroller->SetProportion(bounds.Height () / itemHeight); 1215 // scroll up if there is empty room on bottom 1216 if (itemHeight < bounds.bottom) { 1217 ScrollBy(0.0, bounds.bottom - itemHeight); 1218 } 1219 } 1220 1221 if (count != 0) { 1222 vertScroller->SetSteps((float)ceil(FirstItem()->Height()), 1223 bounds.Height()); 1224 } 1225 } 1226 1227 // _InvalidateFrom 1228 void 1229 BListView::_InvalidateFrom(int32 index) 1230 { 1231 // make sure index is behind last valid index 1232 int32 count = CountItems(); 1233 if (index >= count) { 1234 index = count; 1235 } 1236 // take the item before the wanted one, 1237 // because that might already be removed 1238 index--; 1239 BRect dirty = Bounds(); 1240 if (index >= 0) { 1241 dirty.top = ItemFrame(index).bottom + 1; 1242 } 1243 Invalidate(dirty); 1244 } 1245 1246 // _FontChanged 1247 void 1248 BListView::_FontChanged() 1249 { 1250 BFont font; 1251 GetFont(&font); 1252 1253 for (int i = 0; i < CountItems (); i ++) 1254 ItemAt(i)->Update(this, &font); 1255 } 1256 1257 1258 /*! 1259 Selects the item at the specified \a index, and returns \c true in 1260 case the selection was changed because of this method. 1261 If \a extend is \c false, all previously selected items are deselected. 1262 */ 1263 bool 1264 BListView::_Select(int32 index, bool extend) 1265 { 1266 if (index < 0 || index >= CountItems()) 1267 return false; 1268 1269 // only lock the window when there is one 1270 BAutolock locker(Window()); 1271 if (Window() && !locker.IsLocked()) 1272 return false; 1273 1274 bool changed = false; 1275 1276 if (fFirstSelected != -1 && !extend) 1277 changed = _DeselectAll(index, index); 1278 1279 BListItem* item = ItemAt(index); 1280 if (!item->IsEnabled() || item->IsSelected()) { 1281 // if the item is already selected, or can't be selected, 1282 // we're done here 1283 return changed; 1284 } 1285 1286 // keep track of first and last selected item 1287 if (fFirstSelected == -1) { 1288 // no previous selection 1289 fFirstSelected = index; 1290 fLastSelected = index; 1291 } else if (index < fFirstSelected) { 1292 fFirstSelected = index; 1293 } else if (index > fLastSelected) { 1294 fLastSelected = index; 1295 } 1296 1297 ItemAt(index)->Select(); 1298 if (Window()) 1299 InvalidateItem(index); 1300 1301 return true; 1302 } 1303 1304 1305 /*! 1306 Selects the items between \a from and \a to, and returns \c true in 1307 case the selection was changed because of this method. 1308 If \a extend is \c false, all previously selected items are deselected. 1309 */ 1310 bool 1311 BListView::_Select(int32 from, int32 to, bool extend) 1312 { 1313 if (to < from) 1314 return false; 1315 1316 BAutolock locker(Window()); 1317 if (Window() && !locker.IsLocked()) 1318 return false; 1319 1320 bool changed = false; 1321 1322 if (fFirstSelected != -1 && !extend) 1323 changed = _DeselectAll(from, to); 1324 1325 if (fFirstSelected == -1) { 1326 fFirstSelected = from; 1327 fLastSelected = to; 1328 } else if (from < fFirstSelected) 1329 fFirstSelected = from; 1330 else if (to > fLastSelected) 1331 fLastSelected = to; 1332 1333 for (int32 i = from; i <= to; ++i) { 1334 BListItem *item = ItemAt(i); 1335 if (item && !item->IsSelected()) { 1336 item->Select(); 1337 if (Window()) 1338 InvalidateItem(i); 1339 changed = true; 1340 } 1341 } 1342 1343 return changed; 1344 } 1345 1346 1347 bool 1348 BListView::_Deselect(int32 index) 1349 { 1350 if (index < 0 || index >= CountItems()) 1351 return false; 1352 1353 BWindow *window = Window(); 1354 BAutolock locker(window); 1355 if (window && !locker.IsLocked()) 1356 return false; 1357 1358 BListItem *item = ItemAt(index); 1359 1360 if (item && item->IsSelected()) { 1361 BRect frame(ItemFrame(index)); 1362 BRect bounds(Bounds()); 1363 1364 item->Deselect(); 1365 1366 if (fFirstSelected == index && fLastSelected == index) { 1367 fFirstSelected = -1; 1368 fLastSelected = -1; 1369 } else { 1370 if (fFirstSelected == index) 1371 fFirstSelected = _CalcFirstSelected(index); 1372 1373 if (fLastSelected == index) 1374 fLastSelected = _CalcLastSelected(index); 1375 } 1376 1377 if (window && bounds.Intersects(frame)) 1378 DrawItem(ItemAt(index), frame, true); 1379 } 1380 1381 return true; 1382 } 1383 1384 1385 bool 1386 BListView::_DeselectAll(int32 exceptFrom, int32 exceptTo) 1387 { 1388 if (fFirstSelected == -1) 1389 return false; 1390 1391 BAutolock locker(Window()); 1392 if (Window() && !locker.IsLocked()) 1393 return false; 1394 1395 bool changed = false; 1396 1397 for (int32 index = fFirstSelected; index <= fLastSelected; index++) { 1398 // don't deselect the items we shouldn't deselect 1399 if (exceptFrom != -1 && exceptFrom <= index && exceptTo >= index) 1400 continue; 1401 1402 BListItem *item = ItemAt(index); 1403 if (item && item->IsSelected()) { 1404 item->Deselect(); 1405 InvalidateItem(index); 1406 changed = true; 1407 } 1408 } 1409 1410 if (!changed) 1411 return false; 1412 1413 if (exceptFrom != -1) { 1414 fFirstSelected = _CalcFirstSelected(exceptFrom); 1415 fLastSelected = _CalcLastSelected(exceptTo); 1416 } else 1417 fFirstSelected = fLastSelected = -1; 1418 1419 return true; 1420 } 1421 1422 // _TryInitiateDrag 1423 bool 1424 BListView::_TryInitiateDrag(BPoint where) 1425 { 1426 if (!fTrack->try_drag | fTrack->item_index < 0) 1427 return false; 1428 1429 BPoint offset = where - fTrack->drag_start; 1430 float dragDistance = sqrtf(offset.x * offset.x + offset.y * offset.y); 1431 1432 if (dragDistance > 5.0) { 1433 fTrack->try_drag = false; 1434 return InitiateDrag(fTrack->drag_start, fTrack->item_index, fTrack->was_selected);; 1435 } 1436 return false; 1437 } 1438 1439 // _CalcFirstSelected 1440 int32 1441 BListView::_CalcFirstSelected(int32 after) 1442 { 1443 if (after >= CountItems()) 1444 return -1; 1445 1446 int32 count = CountItems(); 1447 for (int32 i = after; i < count; i++) { 1448 if (ItemAt(i)->IsSelected()) 1449 return i; 1450 } 1451 1452 return -1; 1453 } 1454 1455 1456 int32 1457 BListView::_CalcLastSelected(int32 before) 1458 { 1459 if (before < 0) 1460 return -1; 1461 1462 before = min_c(CountItems() - 1, before); 1463 1464 for (int32 i = before; i >= 0; i--) { 1465 if (ItemAt(i)->IsSelected()) 1466 return i; 1467 } 1468 1469 return -1; 1470 } 1471 1472 1473 void 1474 BListView::DrawItem(BListItem *item, BRect itemRect, bool complete) 1475 { 1476 item->DrawItem(this, itemRect, complete); 1477 } 1478 1479 1480 bool 1481 BListView::_SwapItems(int32 a, int32 b) 1482 { 1483 // remember frames of items before anyhing happens, 1484 // the tricky situation is when the two items have 1485 // a different height 1486 BRect aFrame = ItemFrame(a); 1487 BRect bFrame = ItemFrame(b); 1488 1489 if (!fList.SwapItems(a, b)) 1490 return false; 1491 1492 if (a == b) { 1493 // nothing to do, but success nevertheless 1494 return true; 1495 } 1496 1497 // track anchor item 1498 if (fAnchorIndex == a) 1499 fAnchorIndex = b; 1500 else if (fAnchorIndex == b) 1501 fAnchorIndex = a; 1502 1503 // track selection 1504 // NOTE: this is only important if the selection status 1505 // of both items is not the same 1506 if (ItemAt(a)->IsSelected() != ItemAt(b)->IsSelected()) { 1507 int32 first = min_c(a, b); 1508 int32 last = max_c(a, b); 1509 if (first < fFirstSelected || last > fLastSelected) { 1510 first = min_c(first, fFirstSelected); 1511 last = max_c(last, fLastSelected); 1512 _RescanSelection(first, last); 1513 } 1514 // though the actually selected items stayed the 1515 // same, the selection has still changed 1516 SelectionChanged(); 1517 } 1518 1519 // take care of invalidation 1520 if (Window()) { 1521 // NOTE: window looper is assumed to be locked! 1522 if (aFrame.Height() != bFrame.Height()) { 1523 // items in between shifted visually 1524 Invalidate(aFrame | bFrame); 1525 } else { 1526 Invalidate(aFrame); 1527 Invalidate(bFrame); 1528 } 1529 } 1530 1531 return true; 1532 } 1533 1534 1535 bool 1536 BListView::_MoveItem(int32 from, int32 to) 1537 { 1538 // remember item frames before doing anything 1539 BRect frameFrom = ItemFrame(from); 1540 BRect frameTo = ItemFrame(to); 1541 1542 if (!fList.MoveItem(from, to)) 1543 return false; 1544 1545 // track anchor item 1546 if (fAnchorIndex == from) 1547 fAnchorIndex = to; 1548 1549 // track selection 1550 if (ItemAt(to)->IsSelected()) { 1551 _RescanSelection(from, to); 1552 // though the actually selected items stayed the 1553 // same, the selection has still changed 1554 SelectionChanged(); 1555 } 1556 1557 // take care of invalidation 1558 if (Window()) { 1559 // NOTE: window looper is assumed to be locked! 1560 Invalidate(frameFrom | frameTo); 1561 } 1562 1563 return true; 1564 } 1565 1566 1567 bool 1568 BListView::_ReplaceItem(int32 index, BListItem *item) 1569 { 1570 if (!item) 1571 return false; 1572 1573 BListItem* old = ItemAt(index); 1574 if (!old) 1575 return false; 1576 1577 BRect frame = ItemFrame(index); 1578 1579 bool selectionChanged = old->IsSelected() != item->IsSelected(); 1580 1581 // replace item 1582 if (!fList.ReplaceItem(index, item)) 1583 return false; 1584 1585 // tack selection 1586 if (selectionChanged) { 1587 int32 start = min_c(fFirstSelected, index); 1588 int32 end = max_c(fLastSelected, index); 1589 _RescanSelection(start, end); 1590 SelectionChanged(); 1591 } 1592 1593 bool itemHeightChanged = frame != ItemFrame(index); 1594 1595 // take care of invalidation 1596 if (Window()) { 1597 // NOTE: window looper is assumed to be locked! 1598 if (itemHeightChanged) 1599 _InvalidateFrom(index); 1600 else 1601 Invalidate(frame); 1602 } 1603 1604 if (itemHeightChanged) 1605 _FixupScrollBar(); 1606 1607 return true; 1608 } 1609 1610 1611 void 1612 BListView::_RescanSelection(int32 from, int32 to) 1613 { 1614 if (from > to) { 1615 int32 tmp = from; 1616 from = to; 1617 to = tmp; 1618 } 1619 1620 from = max_c(0, from); 1621 to = min_c(to, CountItems() - 1); 1622 1623 if (fAnchorIndex != -1) { 1624 if (fAnchorIndex == from) 1625 fAnchorIndex = to; 1626 else if (fAnchorIndex == to) 1627 fAnchorIndex = from; 1628 } 1629 1630 for (int32 i = from; i <= to; i++) { 1631 if (ItemAt(i)->IsSelected()) { 1632 fFirstSelected = i; 1633 break; 1634 } 1635 } 1636 1637 if (fFirstSelected > from) 1638 from = fFirstSelected; 1639 for (int32 i = from; i <= to; i++) { 1640 if (ItemAt(i)->IsSelected()) 1641 fLastSelected = i; 1642 } 1643 } 1644 1645 1646