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