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 BMessage* message = Looper()->CurrentMessage(); 550 int32 index = IndexOf(where); 551 552 int32 buttons = 0; 553 if (message != NULL) 554 message->FindInt32("buttons", &buttons); 555 556 int32 modifiers = 0; 557 if (message != NULL) 558 message->FindInt32("modifiers", &modifiers); 559 560 // If the user double (or more) clicked within the current selection, 561 // we don't change the selection but invoke the selection. 562 // TODO: move this code someplace where it can be shared everywhere 563 // instead of every class having to reimplement it, once some sane 564 // API for it is decided. 565 BPoint delta = where - fTrack->drag_start; 566 bigtime_t sysTime; 567 Window()->CurrentMessage()->FindInt64("when", &sysTime); 568 bigtime_t timeDelta = sysTime - fTrack->last_click_time; 569 bigtime_t doubleClickSpeed; 570 get_click_speed(&doubleClickSpeed); 571 bool doubleClick = false; 572 573 if (timeDelta < doubleClickSpeed 574 && fabs(delta.x) < kDoubleClickThreshold 575 && fabs(delta.y) < kDoubleClickThreshold 576 && fTrack->item_index == index) { 577 doubleClick = true; 578 } 579 580 if (doubleClick && index >= fFirstSelected && index <= fLastSelected) { 581 fTrack->drag_start.Set(INT32_MAX, INT32_MAX); 582 Invoke(); 583 return BView::MouseDown(where); 584 } 585 586 if (!doubleClick) { 587 fTrack->drag_start = where; 588 fTrack->last_click_time = system_time(); 589 fTrack->item_index = index; 590 fTrack->was_selected = index >= 0 ? ItemAt(index)->IsSelected() : false; 591 fTrack->try_drag = true; 592 593 MouseDownThread<BListView>::TrackMouse(this, 594 &BListView::_DoneTracking, &BListView::_Track); 595 } 596 597 if (index >= 0) { 598 if (fListType == B_MULTIPLE_SELECTION_LIST) { 599 if ((modifiers & B_SHIFT_KEY) != 0) { 600 // select entire block 601 // TODO: maybe review if we want it like in Tracker 602 // (anchor item) 603 if (index >= fFirstSelected && index < fLastSelected) { 604 // clicked inside of selected items block, deselect all 605 // but from the first selected item to the clicked item 606 DeselectExcept(fFirstSelected, index); 607 } else { 608 Select(std::min(index, fFirstSelected), std::max(index, 609 fLastSelected)); 610 } 611 } else { 612 if ((modifiers & B_COMMAND_KEY) != 0) { 613 // toggle selection state of clicked item (like in Tracker) 614 // toggle selection state of clicked item 615 if (ItemAt(index)->IsSelected()) 616 Deselect(index); 617 else 618 Select(index, true); 619 } else 620 Select(index); 621 } 622 } else { 623 // toggle selection state of clicked item 624 if ((modifiers & B_COMMAND_KEY) != 0 && ItemAt(index)->IsSelected()) 625 Deselect(index); 626 else 627 Select(index); 628 } 629 } else if ((modifiers & B_COMMAND_KEY) == 0) 630 DeselectAll(); 631 632 BView::MouseDown(where); 633 } 634 635 636 void 637 BListView::MouseUp(BPoint where) 638 { 639 BView::MouseUp(where); 640 } 641 642 643 void 644 BListView::MouseMoved(BPoint where, uint32 code, const BMessage* dragMessage) 645 { 646 BView::MouseMoved(where, code, dragMessage); 647 } 648 649 650 bool 651 BListView::InitiateDrag(BPoint where, int32 index, bool wasSelected) 652 { 653 return false; 654 } 655 656 657 // #pragma mark - 658 659 660 void 661 BListView::ResizeToPreferred() 662 { 663 BView::ResizeToPreferred(); 664 } 665 666 667 void 668 BListView::GetPreferredSize(float *_width, float *_height) 669 { 670 int32 count = CountItems(); 671 672 if (count > 0) { 673 float maxWidth = 0.0; 674 for (int32 i = 0; i < count; i++) { 675 float itemWidth = ItemAt(i)->Width(); 676 if (itemWidth > maxWidth) 677 maxWidth = itemWidth; 678 } 679 680 if (_width != NULL) 681 *_width = maxWidth; 682 if (_height != NULL) 683 *_height = ItemAt(count - 1)->Bottom(); 684 } else 685 BView::GetPreferredSize(_width, _height); 686 } 687 688 689 BSize 690 BListView::MinSize() 691 { 692 // We need a stable min size: the BView implementation uses 693 // GetPreferredSize(), which by default just returns the current size. 694 return BLayoutUtils::ComposeSize(ExplicitMinSize(), BSize(10, 10)); 695 } 696 697 698 BSize 699 BListView::MaxSize() 700 { 701 return BView::MaxSize(); 702 } 703 704 705 BSize 706 BListView::PreferredSize() 707 { 708 // We need a stable preferred size: the BView implementation uses 709 // GetPreferredSize(), which by default just returns the current size. 710 return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), BSize(100, 50)); 711 } 712 713 714 // #pragma mark - 715 716 717 void 718 BListView::MakeFocus(bool focused) 719 { 720 if (IsFocus() == focused) 721 return; 722 723 BView::MakeFocus(focused); 724 725 if (fScrollView) 726 fScrollView->SetBorderHighlighted(focused); 727 } 728 729 730 void 731 BListView::SetFont(const BFont* font, uint32 mask) 732 { 733 BView::SetFont(font, mask); 734 735 if (Window() != NULL && !Window()->InViewTransaction()) 736 _UpdateItems(); 737 } 738 739 740 void 741 BListView::ScrollTo(BPoint point) 742 { 743 BView::ScrollTo(point); 744 } 745 746 747 // #pragma mark - List ops 748 749 750 bool 751 BListView::AddItem(BListItem* item, int32 index) 752 { 753 if (!fList.AddItem(item, index)) 754 return false; 755 756 if (fFirstSelected != -1 && index <= fFirstSelected) 757 fFirstSelected++; 758 759 if (fLastSelected != -1 && index <= fLastSelected) 760 fLastSelected++; 761 762 if (Window()) { 763 BFont font; 764 GetFont(&font); 765 item->SetTop((index > 0) ? ItemAt(index - 1)->Bottom() + 1.0 : 0.0); 766 767 item->Update(this, &font); 768 _RecalcItemTops(index + 1); 769 770 _FixupScrollBar(); 771 _InvalidateFrom(index); 772 } 773 774 return true; 775 } 776 777 778 bool 779 BListView::AddItem(BListItem* item) 780 { 781 if (!fList.AddItem(item)) 782 return false; 783 784 // No need to adapt selection, as this item is the last in the list 785 786 if (Window()) { 787 BFont font; 788 GetFont(&font); 789 int32 index = CountItems() - 1; 790 item->SetTop((index > 0) ? ItemAt(index - 1)->Bottom() + 1.0 : 0.0); 791 792 item->Update(this, &font); 793 794 _FixupScrollBar(); 795 InvalidateItem(CountItems() - 1); 796 } 797 798 return true; 799 } 800 801 802 bool 803 BListView::AddList(BList* list, int32 index) 804 { 805 if (!fList.AddList(list, index)) 806 return false; 807 808 int32 count = list->CountItems(); 809 810 if (fFirstSelected != -1 && index < fFirstSelected) 811 fFirstSelected += count; 812 813 if (fLastSelected != -1 && index < fLastSelected) 814 fLastSelected += count; 815 816 if (Window()) { 817 BFont font; 818 GetFont(&font); 819 820 for (int32 i = index; i <= (index + count - 1); i++) { 821 ItemAt(i)->SetTop((i > 0) ? ItemAt(i - 1)->Bottom() + 1.0 : 0.0); 822 ItemAt(i)->Update(this, &font); 823 } 824 825 _RecalcItemTops(index + count - 1); 826 827 _FixupScrollBar(); 828 Invalidate(); // TODO 829 } 830 831 return true; 832 } 833 834 835 bool 836 BListView::AddList(BList* list) 837 { 838 return AddList(list, CountItems()); 839 } 840 841 842 BListItem* 843 BListView::RemoveItem(int32 index) 844 { 845 BListItem* item = ItemAt(index); 846 if (item == NULL) 847 return NULL; 848 849 if (item->IsSelected()) 850 Deselect(index); 851 852 if (!fList.RemoveItem(item)) 853 return NULL; 854 855 if (fFirstSelected != -1 && index < fFirstSelected) 856 fFirstSelected--; 857 858 if (fLastSelected != -1 && index < fLastSelected) 859 fLastSelected--; 860 861 if (fAnchorIndex != -1 && index < fAnchorIndex) 862 fAnchorIndex--; 863 864 _RecalcItemTops(index); 865 866 _InvalidateFrom(index); 867 _FixupScrollBar(); 868 869 return item; 870 } 871 872 873 bool 874 BListView::RemoveItem(BListItem* item) 875 { 876 return BListView::RemoveItem(IndexOf(item)) != NULL; 877 } 878 879 880 bool 881 BListView::RemoveItems(int32 index, int32 count) 882 { 883 if (index >= fList.CountItems()) 884 index = -1; 885 886 if (index < 0) 887 return false; 888 889 if (fAnchorIndex != -1 && index < fAnchorIndex) 890 fAnchorIndex = index; 891 892 fList.RemoveItems(index, count); 893 if (index < fList.CountItems()) 894 _RecalcItemTops(index); 895 896 Invalidate(); 897 return true; 898 } 899 900 901 void 902 BListView::SetSelectionMessage(BMessage* message) 903 { 904 delete fSelectMessage; 905 fSelectMessage = message; 906 } 907 908 909 void 910 BListView::SetInvocationMessage(BMessage* message) 911 { 912 BInvoker::SetMessage(message); 913 } 914 915 916 BMessage* 917 BListView::InvocationMessage() const 918 { 919 return BInvoker::Message(); 920 } 921 922 923 uint32 924 BListView::InvocationCommand() const 925 { 926 return BInvoker::Command(); 927 } 928 929 930 BMessage* 931 BListView::SelectionMessage() const 932 { 933 return fSelectMessage; 934 } 935 936 937 uint32 938 BListView::SelectionCommand() const 939 { 940 if (fSelectMessage) 941 return fSelectMessage->what; 942 943 return 0; 944 } 945 946 947 void 948 BListView::SetListType(list_view_type type) 949 { 950 if (fListType == B_MULTIPLE_SELECTION_LIST 951 && type == B_SINGLE_SELECTION_LIST) { 952 Select(CurrentSelection(0)); 953 } 954 955 fListType = type; 956 } 957 958 959 list_view_type 960 BListView::ListType() const 961 { 962 return fListType; 963 } 964 965 966 BListItem* 967 BListView::ItemAt(int32 index) const 968 { 969 return (BListItem*)fList.ItemAt(index); 970 } 971 972 973 int32 974 BListView::IndexOf(BListItem* item) const 975 { 976 if (Window()) { 977 if (item != NULL) { 978 int32 index = IndexOf(BPoint(0.0, item->Top())); 979 if (index >= 0 && fList.ItemAt(index) == item) 980 return index; 981 982 return -1; 983 } 984 } 985 return fList.IndexOf(item); 986 } 987 988 989 int32 990 BListView::IndexOf(BPoint point) const 991 { 992 int32 low = 0; 993 int32 high = fList.CountItems() - 1; 994 int32 mid = -1; 995 float frameTop = -1.0; 996 float frameBottom = 1.0; 997 998 // binary search the list 999 while (high >= low) { 1000 mid = (low + high) / 2; 1001 frameTop = ItemAt(mid)->Top(); 1002 frameBottom = ItemAt(mid)->Bottom(); 1003 if (point.y < frameTop) 1004 high = mid - 1; 1005 else if (point.y > frameBottom) 1006 low = mid + 1; 1007 else 1008 return mid; 1009 } 1010 1011 return -1; 1012 } 1013 1014 1015 BListItem* 1016 BListView::FirstItem() const 1017 { 1018 return (BListItem*)fList.FirstItem(); 1019 } 1020 1021 1022 BListItem* 1023 BListView::LastItem() const 1024 { 1025 return (BListItem*)fList.LastItem(); 1026 } 1027 1028 1029 bool 1030 BListView::HasItem(BListItem *item) const 1031 { 1032 return IndexOf(item) != -1; 1033 } 1034 1035 1036 int32 1037 BListView::CountItems() const 1038 { 1039 return fList.CountItems(); 1040 } 1041 1042 1043 void 1044 BListView::MakeEmpty() 1045 { 1046 if (fList.IsEmpty()) 1047 return; 1048 1049 _DeselectAll(-1, -1); 1050 fList.MakeEmpty(); 1051 1052 if (Window()) { 1053 _FixupScrollBar(); 1054 Invalidate(); 1055 } 1056 } 1057 1058 1059 bool 1060 BListView::IsEmpty() const 1061 { 1062 return fList.IsEmpty(); 1063 } 1064 1065 1066 void 1067 BListView::DoForEach(bool (*func)(BListItem*)) 1068 { 1069 fList.DoForEach(reinterpret_cast<bool (*)(void*)>(func)); 1070 } 1071 1072 1073 void 1074 BListView::DoForEach(bool (*func)(BListItem*, void*), void* arg) 1075 { 1076 fList.DoForEach(reinterpret_cast<bool (*)(void*, void*)>(func), arg); 1077 } 1078 1079 1080 const BListItem** 1081 BListView::Items() const 1082 { 1083 return (const BListItem**)fList.Items(); 1084 } 1085 1086 1087 void 1088 BListView::InvalidateItem(int32 index) 1089 { 1090 Invalidate(ItemFrame(index)); 1091 } 1092 1093 1094 void 1095 BListView::ScrollToSelection() 1096 { 1097 BRect itemFrame = ItemFrame(CurrentSelection(0)); 1098 1099 if (Bounds().Contains(itemFrame)) 1100 return; 1101 1102 float scrollPos = itemFrame.top < Bounds().top ? 1103 itemFrame.top : itemFrame.bottom - Bounds().Height(); 1104 1105 if (itemFrame.top - scrollPos < Bounds().top) 1106 scrollPos = itemFrame.top; 1107 1108 ScrollTo(itemFrame.left, scrollPos); 1109 } 1110 1111 1112 void 1113 BListView::Select(int32 index, bool extend) 1114 { 1115 if (_Select(index, extend)) { 1116 SelectionChanged(); 1117 InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED); 1118 } 1119 } 1120 1121 1122 void 1123 BListView::Select(int32 start, int32 finish, bool extend) 1124 { 1125 if (_Select(start, finish, extend)) { 1126 SelectionChanged(); 1127 InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED); 1128 } 1129 } 1130 1131 1132 bool 1133 BListView::IsItemSelected(int32 index) const 1134 { 1135 BListItem* item = ItemAt(index); 1136 if (item != NULL) 1137 return item->IsSelected(); 1138 1139 return false; 1140 } 1141 1142 1143 int32 1144 BListView::CurrentSelection(int32 index) const 1145 { 1146 if (fFirstSelected == -1) 1147 return -1; 1148 1149 if (index == 0) 1150 return fFirstSelected; 1151 1152 for (int32 i = fFirstSelected; i <= fLastSelected; i++) { 1153 if (ItemAt(i)->IsSelected()) { 1154 if (index == 0) 1155 return i; 1156 1157 index--; 1158 } 1159 } 1160 1161 return -1; 1162 } 1163 1164 1165 status_t 1166 BListView::Invoke(BMessage* message) 1167 { 1168 // Note, this is more or less a copy of BControl::Invoke() and should 1169 // stay that way (ie. changes done there should be adopted here) 1170 1171 bool notify = false; 1172 uint32 kind = InvokeKind(¬ify); 1173 1174 BMessage clone(kind); 1175 status_t err = B_BAD_VALUE; 1176 1177 if (!message && !notify) 1178 message = Message(); 1179 1180 if (!message) { 1181 if (!IsWatched()) 1182 return err; 1183 } else 1184 clone = *message; 1185 1186 clone.AddInt64("when", (int64)system_time()); 1187 clone.AddPointer("source", this); 1188 clone.AddMessenger("be:sender", BMessenger(this)); 1189 1190 if (fListType == B_SINGLE_SELECTION_LIST) 1191 clone.AddInt32("index", fFirstSelected); 1192 else { 1193 if (fFirstSelected >= 0) { 1194 for (int32 i = fFirstSelected; i <= fLastSelected; i++) { 1195 if (ItemAt(i)->IsSelected()) 1196 clone.AddInt32("index", i); 1197 } 1198 } 1199 } 1200 1201 if (message) 1202 err = BInvoker::Invoke(&clone); 1203 1204 SendNotices(kind, &clone); 1205 1206 return err; 1207 } 1208 1209 1210 void 1211 BListView::DeselectAll() 1212 { 1213 if (_DeselectAll(-1, -1)) { 1214 SelectionChanged(); 1215 InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED); 1216 } 1217 } 1218 1219 1220 void 1221 BListView::DeselectExcept(int32 exceptFrom, int32 exceptTo) 1222 { 1223 if (exceptFrom > exceptTo || exceptFrom < 0 || exceptTo < 0) 1224 return; 1225 1226 if (_DeselectAll(exceptFrom, exceptTo)) { 1227 SelectionChanged(); 1228 InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED); 1229 } 1230 } 1231 1232 1233 void 1234 BListView::Deselect(int32 index) 1235 { 1236 if (_Deselect(index)) { 1237 SelectionChanged(); 1238 InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED); 1239 } 1240 } 1241 1242 1243 void 1244 BListView::SelectionChanged() 1245 { 1246 // Hook method to be implemented by subclasses 1247 } 1248 1249 1250 void 1251 BListView::SortItems(int (*cmp)(const void *, const void *)) 1252 { 1253 if (_DeselectAll(-1, -1)) { 1254 SelectionChanged(); 1255 InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED); 1256 } 1257 1258 fList.SortItems(cmp); 1259 _RecalcItemTops(0); 1260 Invalidate(); 1261 } 1262 1263 1264 bool 1265 BListView::SwapItems(int32 a, int32 b) 1266 { 1267 MiscData data; 1268 1269 data.swap.a = a; 1270 data.swap.b = b; 1271 1272 return DoMiscellaneous(B_SWAP_OP, &data); 1273 } 1274 1275 1276 bool 1277 BListView::MoveItem(int32 from, int32 to) 1278 { 1279 MiscData data; 1280 1281 data.move.from = from; 1282 data.move.to = to; 1283 1284 return DoMiscellaneous(B_MOVE_OP, &data); 1285 } 1286 1287 1288 bool 1289 BListView::ReplaceItem(int32 index, BListItem* item) 1290 { 1291 MiscData data; 1292 1293 data.replace.index = index; 1294 data.replace.item = item; 1295 1296 return DoMiscellaneous(B_REPLACE_OP, &data); 1297 } 1298 1299 1300 BRect 1301 BListView::ItemFrame(int32 index) 1302 { 1303 BRect frame = Bounds(); 1304 if (index < 0 || index >= CountItems()) { 1305 frame.top = 0; 1306 frame.bottom = -1; 1307 } else { 1308 BListItem* item = ItemAt(index); 1309 frame.top = item->Top(); 1310 frame.bottom = item->Bottom(); 1311 } 1312 return frame; 1313 } 1314 1315 1316 // #pragma mark - 1317 1318 1319 BHandler* 1320 BListView::ResolveSpecifier(BMessage* message, int32 index, 1321 BMessage* specifier, int32 what, const char* property) 1322 { 1323 BPropertyInfo propInfo(sProperties); 1324 1325 if (propInfo.FindMatch(message, 0, specifier, what, property) < 0) { 1326 return BView::ResolveSpecifier(message, index, specifier, what, 1327 property); 1328 } 1329 1330 // TODO: msg->AddInt32("_match_code_", ); 1331 1332 return this; 1333 } 1334 1335 1336 status_t 1337 BListView::GetSupportedSuites(BMessage* data) 1338 { 1339 if (data == NULL) 1340 return B_BAD_VALUE; 1341 1342 status_t err = data->AddString("suites", "suite/vnd.Be-list-view"); 1343 1344 BPropertyInfo propertyInfo(sProperties); 1345 if (err == B_OK) 1346 err = data->AddFlat("messages", &propertyInfo); 1347 1348 if (err == B_OK) 1349 return BView::GetSupportedSuites(data); 1350 return err; 1351 } 1352 1353 1354 status_t 1355 BListView::Perform(perform_code code, void* _data) 1356 { 1357 switch (code) { 1358 case PERFORM_CODE_MIN_SIZE: 1359 ((perform_data_min_size*)_data)->return_value 1360 = BListView::MinSize(); 1361 return B_OK; 1362 case PERFORM_CODE_MAX_SIZE: 1363 ((perform_data_max_size*)_data)->return_value 1364 = BListView::MaxSize(); 1365 return B_OK; 1366 case PERFORM_CODE_PREFERRED_SIZE: 1367 ((perform_data_preferred_size*)_data)->return_value 1368 = BListView::PreferredSize(); 1369 return B_OK; 1370 case PERFORM_CODE_LAYOUT_ALIGNMENT: 1371 ((perform_data_layout_alignment*)_data)->return_value 1372 = BListView::LayoutAlignment(); 1373 return B_OK; 1374 case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH: 1375 ((perform_data_has_height_for_width*)_data)->return_value 1376 = BListView::HasHeightForWidth(); 1377 return B_OK; 1378 case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH: 1379 { 1380 perform_data_get_height_for_width* data 1381 = (perform_data_get_height_for_width*)_data; 1382 BListView::GetHeightForWidth(data->width, &data->min, &data->max, 1383 &data->preferred); 1384 return B_OK; 1385 } 1386 case PERFORM_CODE_SET_LAYOUT: 1387 { 1388 perform_data_set_layout* data = (perform_data_set_layout*)_data; 1389 BListView::SetLayout(data->layout); 1390 return B_OK; 1391 } 1392 case PERFORM_CODE_LAYOUT_INVALIDATED: 1393 { 1394 perform_data_layout_invalidated* data 1395 = (perform_data_layout_invalidated*)_data; 1396 BListView::LayoutInvalidated(data->descendants); 1397 return B_OK; 1398 } 1399 case PERFORM_CODE_DO_LAYOUT: 1400 { 1401 BListView::DoLayout(); 1402 return B_OK; 1403 } 1404 } 1405 1406 return BView::Perform(code, _data); 1407 } 1408 1409 1410 bool 1411 BListView::DoMiscellaneous(MiscCode code, MiscData* data) 1412 { 1413 if (code > B_SWAP_OP) 1414 return false; 1415 1416 switch (code) { 1417 case B_NO_OP: 1418 break; 1419 1420 case B_REPLACE_OP: 1421 return _ReplaceItem(data->replace.index, data->replace.item); 1422 1423 case B_MOVE_OP: 1424 return _MoveItem(data->move.from, data->move.to); 1425 1426 case B_SWAP_OP: 1427 return _SwapItems(data->swap.a, data->swap.b); 1428 } 1429 1430 return false; 1431 } 1432 1433 1434 // #pragma mark - 1435 1436 1437 void BListView::_ReservedListView2() {} 1438 void BListView::_ReservedListView3() {} 1439 void BListView::_ReservedListView4() {} 1440 1441 1442 BListView& 1443 BListView::operator=(const BListView& /*other*/) 1444 { 1445 return *this; 1446 } 1447 1448 1449 // #pragma mark - 1450 1451 1452 void 1453 BListView::_InitObject(list_view_type type) 1454 { 1455 fListType = type; 1456 fFirstSelected = -1; 1457 fLastSelected = -1; 1458 fAnchorIndex = -1; 1459 fSelectMessage = NULL; 1460 fScrollView = NULL; 1461 1462 fTrack = new track_data; 1463 fTrack->drag_start = B_ORIGIN; 1464 fTrack->item_index = -1; 1465 fTrack->was_selected = false; 1466 fTrack->try_drag = false; 1467 fTrack->is_dragging = false; 1468 fTrack->last_click_time = 0; 1469 1470 SetViewUIColor(B_LIST_BACKGROUND_COLOR); 1471 SetLowUIColor(B_LIST_BACKGROUND_COLOR); 1472 } 1473 1474 1475 void 1476 BListView::_FixupScrollBar() 1477 { 1478 1479 BScrollBar* vertScroller = ScrollBar(B_VERTICAL); 1480 if (vertScroller != NULL) { 1481 BRect bounds = Bounds(); 1482 int32 count = CountItems(); 1483 1484 float itemHeight = 0.0; 1485 1486 if (CountItems() > 0) 1487 itemHeight = ItemAt(CountItems() - 1)->Bottom(); 1488 1489 if (bounds.Height() > itemHeight) { 1490 // no scrolling 1491 vertScroller->SetRange(0.0, 0.0); 1492 vertScroller->SetValue(0.0); 1493 // also scrolls ListView to the top 1494 } else { 1495 vertScroller->SetRange(0.0, itemHeight - bounds.Height() - 1.0); 1496 vertScroller->SetProportion(bounds.Height () / itemHeight); 1497 // scroll up if there is empty room on bottom 1498 if (itemHeight < bounds.bottom) 1499 ScrollBy(0.0, bounds.bottom - itemHeight); 1500 } 1501 1502 if (count != 0) 1503 vertScroller->SetSteps( 1504 ceilf(FirstItem()->Height()), bounds.Height()); 1505 } 1506 1507 BScrollBar* horizontalScroller = ScrollBar(B_HORIZONTAL); 1508 if (horizontalScroller != NULL) { 1509 float w; 1510 GetPreferredSize(&w, NULL); 1511 BRect scrollBarSize = horizontalScroller->Bounds(); 1512 1513 if (w <= scrollBarSize.Width()) { 1514 // no scrolling 1515 horizontalScroller->SetRange(0.0, 0.0); 1516 horizontalScroller->SetValue(0.0); 1517 } else { 1518 horizontalScroller->SetRange(0, w - scrollBarSize.Width()); 1519 horizontalScroller->SetProportion(scrollBarSize.Width() / w); 1520 } 1521 printf("Range: %f - %f\n", w, scrollBarSize.Width()); 1522 } 1523 } 1524 1525 1526 void 1527 BListView::_InvalidateFrom(int32 index) 1528 { 1529 // make sure index is behind last valid index 1530 int32 count = CountItems(); 1531 if (index >= count) 1532 index = count; 1533 1534 // take the item before the wanted one, 1535 // because that might already be removed 1536 index--; 1537 BRect dirty = Bounds(); 1538 if (index >= 0) 1539 dirty.top = ItemFrame(index).bottom + 1; 1540 1541 Invalidate(dirty); 1542 } 1543 1544 1545 void 1546 BListView::_UpdateItems() 1547 { 1548 BFont font; 1549 GetFont(&font); 1550 for (int32 i = 0; i < CountItems(); i++) { 1551 ItemAt(i)->SetTop((i > 0) ? ItemAt(i - 1)->Bottom() + 1.0 : 0.0); 1552 ItemAt(i)->Update(this, &font); 1553 } 1554 } 1555 1556 1557 /*! Selects the item at the specified \a index, and returns \c true in 1558 case the selection was changed because of this method. 1559 If \a extend is \c false, all previously selected items are deselected. 1560 */ 1561 bool 1562 BListView::_Select(int32 index, bool extend) 1563 { 1564 if (index < 0 || index >= CountItems()) 1565 return false; 1566 1567 // only lock the window when there is one 1568 BAutolock locker(Window()); 1569 if (Window() != NULL && !locker.IsLocked()) 1570 return false; 1571 1572 bool changed = false; 1573 1574 if (!extend && fFirstSelected != -1) 1575 changed = _DeselectAll(index, index); 1576 1577 fAnchorIndex = index; 1578 1579 BListItem* item = ItemAt(index); 1580 if (!item->IsEnabled() || item->IsSelected()) { 1581 // if the item is already selected, or can't be selected, 1582 // we're done here 1583 return changed; 1584 } 1585 1586 // keep track of first and last selected item 1587 if (fFirstSelected == -1) { 1588 // no previous selection 1589 fFirstSelected = index; 1590 fLastSelected = index; 1591 } else if (index < fFirstSelected) { 1592 fFirstSelected = index; 1593 } else if (index > fLastSelected) { 1594 fLastSelected = index; 1595 } 1596 1597 item->Select(); 1598 if (Window() != NULL) 1599 InvalidateItem(index); 1600 1601 return true; 1602 } 1603 1604 1605 /*! 1606 Selects the items between \a from and \a to, and returns \c true in 1607 case the selection was changed because of this method. 1608 If \a extend is \c false, all previously selected items are deselected. 1609 */ 1610 bool 1611 BListView::_Select(int32 from, int32 to, bool extend) 1612 { 1613 if (to < from) 1614 return false; 1615 1616 BAutolock locker(Window()); 1617 if (Window() && !locker.IsLocked()) 1618 return false; 1619 1620 bool changed = false; 1621 1622 if (fFirstSelected != -1 && !extend) 1623 changed = _DeselectAll(from, to); 1624 1625 if (fFirstSelected == -1) { 1626 fFirstSelected = from; 1627 fLastSelected = to; 1628 } else { 1629 if (from < fFirstSelected) 1630 fFirstSelected = from; 1631 if (to > fLastSelected) 1632 fLastSelected = to; 1633 } 1634 1635 for (int32 i = from; i <= to; ++i) { 1636 BListItem* item = ItemAt(i); 1637 if (item != NULL && !item->IsSelected() && item->IsEnabled()) { 1638 item->Select(); 1639 if (Window() != NULL) 1640 InvalidateItem(i); 1641 changed = true; 1642 } 1643 } 1644 1645 return changed; 1646 } 1647 1648 1649 bool 1650 BListView::_Deselect(int32 index) 1651 { 1652 if (index < 0 || index >= CountItems()) 1653 return false; 1654 1655 BWindow* window = Window(); 1656 BAutolock locker(window); 1657 if (window != NULL && !locker.IsLocked()) 1658 return false; 1659 1660 BListItem* item = ItemAt(index); 1661 1662 if (item != NULL && item->IsSelected()) { 1663 BRect frame(ItemFrame(index)); 1664 BRect bounds(Bounds()); 1665 1666 item->Deselect(); 1667 1668 if (fFirstSelected == index && fLastSelected == index) { 1669 fFirstSelected = -1; 1670 fLastSelected = -1; 1671 } else { 1672 if (fFirstSelected == index) 1673 fFirstSelected = _CalcFirstSelected(index); 1674 1675 if (fLastSelected == index) 1676 fLastSelected = _CalcLastSelected(index); 1677 } 1678 1679 if (window && bounds.Intersects(frame)) 1680 DrawItem(ItemAt(index), frame, true); 1681 } 1682 1683 return true; 1684 } 1685 1686 1687 bool 1688 BListView::_DeselectAll(int32 exceptFrom, int32 exceptTo) 1689 { 1690 if (fFirstSelected == -1) 1691 return false; 1692 1693 BAutolock locker(Window()); 1694 if (Window() && !locker.IsLocked()) 1695 return false; 1696 1697 bool changed = false; 1698 1699 for (int32 index = fFirstSelected; index <= fLastSelected; index++) { 1700 // don't deselect the items we shouldn't deselect 1701 if (exceptFrom != -1 && exceptFrom <= index && exceptTo >= index) 1702 continue; 1703 1704 BListItem* item = ItemAt(index); 1705 if (item != NULL && item->IsSelected()) { 1706 item->Deselect(); 1707 InvalidateItem(index); 1708 changed = true; 1709 } 1710 } 1711 1712 if (!changed) 1713 return false; 1714 1715 if (exceptFrom != -1) { 1716 fFirstSelected = _CalcFirstSelected(exceptFrom); 1717 fLastSelected = _CalcLastSelected(exceptTo); 1718 } else 1719 fFirstSelected = fLastSelected = -1; 1720 1721 return true; 1722 } 1723 1724 1725 int32 1726 BListView::_CalcFirstSelected(int32 after) 1727 { 1728 if (after >= CountItems()) 1729 return -1; 1730 1731 int32 count = CountItems(); 1732 for (int32 i = after; i < count; i++) { 1733 if (ItemAt(i)->IsSelected()) 1734 return i; 1735 } 1736 1737 return -1; 1738 } 1739 1740 1741 int32 1742 BListView::_CalcLastSelected(int32 before) 1743 { 1744 if (before < 0) 1745 return -1; 1746 1747 before = std::min(CountItems() - 1, before); 1748 1749 for (int32 i = before; i >= 0; i--) { 1750 if (ItemAt(i)->IsSelected()) 1751 return i; 1752 } 1753 1754 return -1; 1755 } 1756 1757 1758 void 1759 BListView::DrawItem(BListItem* item, BRect itemRect, bool complete) 1760 { 1761 if (!item->IsEnabled()) { 1762 rgb_color textColor = ui_color(B_LIST_ITEM_TEXT_COLOR); 1763 rgb_color disabledColor; 1764 if (textColor.red + textColor.green + textColor.blue > 128 * 3) 1765 disabledColor = tint_color(textColor, B_DARKEN_2_TINT); 1766 else 1767 disabledColor = tint_color(textColor, B_LIGHTEN_2_TINT); 1768 1769 SetHighColor(disabledColor); 1770 } else if (item->IsSelected()) 1771 SetHighColor(ui_color(B_LIST_SELECTED_ITEM_TEXT_COLOR)); 1772 else 1773 SetHighColor(ui_color(B_LIST_ITEM_TEXT_COLOR)); 1774 1775 item->DrawItem(this, itemRect, complete); 1776 } 1777 1778 1779 bool 1780 BListView::_SwapItems(int32 a, int32 b) 1781 { 1782 // remember frames of items before anything happens, 1783 // the tricky situation is when the two items have 1784 // a different height 1785 BRect aFrame = ItemFrame(a); 1786 BRect bFrame = ItemFrame(b); 1787 1788 if (!fList.SwapItems(a, b)) 1789 return false; 1790 1791 if (a == b) { 1792 // nothing to do, but success nevertheless 1793 return true; 1794 } 1795 1796 // track anchor item 1797 if (fAnchorIndex == a) 1798 fAnchorIndex = b; 1799 else if (fAnchorIndex == b) 1800 fAnchorIndex = a; 1801 1802 // track selection 1803 // NOTE: this is only important if the selection status 1804 // of both items is not the same 1805 int32 first = std::min(a, b); 1806 int32 last = std::max(a, b); 1807 if (ItemAt(a)->IsSelected() != ItemAt(b)->IsSelected()) { 1808 if (first < fFirstSelected || last > fLastSelected) { 1809 _RescanSelection(std::min(first, fFirstSelected), 1810 std::max(last, fLastSelected)); 1811 } 1812 // though the actually selected items stayed the 1813 // same, the selection has still changed 1814 SelectionChanged(); 1815 } 1816 1817 ItemAt(a)->SetTop(aFrame.top); 1818 ItemAt(b)->SetTop(bFrame.top); 1819 1820 // take care of invalidation 1821 if (Window()) { 1822 // NOTE: window looper is assumed to be locked! 1823 if (aFrame.Height() != bFrame.Height()) { 1824 _RecalcItemTops(first, last); 1825 // items in between shifted visually 1826 Invalidate(aFrame | bFrame); 1827 } else { 1828 Invalidate(aFrame); 1829 Invalidate(bFrame); 1830 } 1831 } 1832 1833 return true; 1834 } 1835 1836 1837 bool 1838 BListView::_MoveItem(int32 from, int32 to) 1839 { 1840 // remember item frames before doing anything 1841 BRect frameFrom = ItemFrame(from); 1842 BRect frameTo = ItemFrame(to); 1843 1844 if (!fList.MoveItem(from, to)) 1845 return false; 1846 1847 // track anchor item 1848 if (fAnchorIndex == from) 1849 fAnchorIndex = to; 1850 1851 // track selection 1852 if (ItemAt(to)->IsSelected()) { 1853 _RescanSelection(from, to); 1854 // though the actually selected items stayed the 1855 // same, the selection has still changed 1856 SelectionChanged(); 1857 } 1858 1859 _RecalcItemTops((to > from) ? from : to); 1860 1861 // take care of invalidation 1862 if (Window()) { 1863 // NOTE: window looper is assumed to be locked! 1864 Invalidate(frameFrom | frameTo); 1865 } 1866 1867 return true; 1868 } 1869 1870 1871 bool 1872 BListView::_ReplaceItem(int32 index, BListItem* item) 1873 { 1874 if (item == NULL) 1875 return false; 1876 1877 BListItem* old = ItemAt(index); 1878 if (!old) 1879 return false; 1880 1881 BRect frame = ItemFrame(index); 1882 1883 bool selectionChanged = old->IsSelected() != item->IsSelected(); 1884 1885 // replace item 1886 if (!fList.ReplaceItem(index, item)) 1887 return false; 1888 1889 // tack selection 1890 if (selectionChanged) { 1891 int32 start = std::min(fFirstSelected, index); 1892 int32 end = std::max(fLastSelected, index); 1893 _RescanSelection(start, end); 1894 SelectionChanged(); 1895 } 1896 _RecalcItemTops(index); 1897 1898 bool itemHeightChanged = frame != ItemFrame(index); 1899 1900 // take care of invalidation 1901 if (Window()) { 1902 // NOTE: window looper is assumed to be locked! 1903 if (itemHeightChanged) 1904 _InvalidateFrom(index); 1905 else 1906 Invalidate(frame); 1907 } 1908 1909 if (itemHeightChanged) 1910 _FixupScrollBar(); 1911 1912 return true; 1913 } 1914 1915 1916 void 1917 BListView::_RescanSelection(int32 from, int32 to) 1918 { 1919 if (from > to) { 1920 int32 tmp = from; 1921 from = to; 1922 to = tmp; 1923 } 1924 1925 from = std::max((int32)0, from); 1926 to = std::min(to, CountItems() - 1); 1927 1928 if (fAnchorIndex != -1) { 1929 if (fAnchorIndex == from) 1930 fAnchorIndex = to; 1931 else if (fAnchorIndex == to) 1932 fAnchorIndex = from; 1933 } 1934 1935 for (int32 i = from; i <= to; i++) { 1936 if (ItemAt(i)->IsSelected()) { 1937 fFirstSelected = i; 1938 break; 1939 } 1940 } 1941 1942 if (fFirstSelected > from) 1943 from = fFirstSelected; 1944 1945 fLastSelected = fFirstSelected; 1946 for (int32 i = from; i <= to; i++) { 1947 if (ItemAt(i)->IsSelected()) 1948 fLastSelected = i; 1949 } 1950 } 1951 1952 1953 void 1954 BListView::_RecalcItemTops(int32 start, int32 end) 1955 { 1956 int32 count = CountItems(); 1957 if ((start < 0) || (start >= count)) 1958 return; 1959 1960 if (end >= 0) 1961 count = end + 1; 1962 1963 float top = (start == 0) ? 0.0 : ItemAt(start - 1)->Bottom() + 1.0; 1964 1965 for (int32 i = start; i < count; i++) { 1966 BListItem *item = ItemAt(i); 1967 item->SetTop(top); 1968 top += ceilf(item->Height()); 1969 } 1970 } 1971 1972 1973 void 1974 BListView::_DoneTracking(BPoint where) 1975 { 1976 fTrack->try_drag = false; 1977 fTrack->is_dragging = false; 1978 } 1979 1980 1981 void 1982 BListView::_Track(BPoint where, uint32) 1983 { 1984 if (fTrack->item_index >= 0 && fTrack->try_drag) { 1985 // initiate a drag if the mouse was moved far enough 1986 BPoint offset = where - fTrack->drag_start; 1987 float dragDistance = sqrtf(offset.x * offset.x + offset.y * offset.y); 1988 if (dragDistance >= 5.0f) { 1989 fTrack->try_drag = false; 1990 fTrack->is_dragging = InitiateDrag(fTrack->drag_start, 1991 fTrack->item_index, fTrack->was_selected); 1992 } 1993 } 1994 1995 if (!fTrack->is_dragging) { 1996 // do selection only if a drag was not initiated 1997 int32 index = IndexOf(where); 1998 BListItem* item = ItemAt(index); 1999 if (item != NULL && !item->IsSelected() && item->IsEnabled()) { 2000 Select(index, fListType == B_MULTIPLE_SELECTION_LIST 2001 && (modifiers() & B_SHIFT_KEY) != 0); 2002 ScrollToSelection(); 2003 } 2004 } 2005 } 2006