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