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