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