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