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