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