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 | B_SCROLL_VIEW_AWARE) 90 { 91 _InitObject(type); 92 } 93 94 95 BListView::BListView(const char* name, list_view_type type, uint32 flags) 96 : 97 BView(name, flags | B_SCROLL_VIEW_AWARE) 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 | B_SCROLL_VIEW_AWARE) 107 { 108 _InitObject(type); 109 } 110 111 112 BListView::BListView(BMessage* archive) 113 : 114 BView(archive) 115 { 116 int32 listType; 117 archive->FindInt32("_lv_type", &listType); 118 _InitObject((list_view_type)listType); 119 120 int32 i = 0; 121 BMessage subData; 122 while (archive->FindMessage("_l_items", i++, &subData) == B_OK) { 123 BArchivable* object = instantiate_object(&subData); 124 if (object == NULL) 125 continue; 126 127 BListItem* item = dynamic_cast<BListItem*>(object); 128 if (item != NULL) 129 AddItem(item); 130 } 131 132 if (archive->HasMessage("_msg")) { 133 BMessage* invokationMessage = new BMessage; 134 135 archive->FindMessage("_msg", invokationMessage); 136 SetInvocationMessage(invokationMessage); 137 } 138 139 if (archive->HasMessage("_2nd_msg")) { 140 BMessage* selectionMessage = new BMessage; 141 142 archive->FindMessage("_2nd_msg", selectionMessage); 143 SetSelectionMessage(selectionMessage); 144 } 145 } 146 147 148 BListView::~BListView() 149 { 150 // NOTE: According to BeBook, BListView does not free the items itself. 151 delete fTrack; 152 SetSelectionMessage(NULL); 153 } 154 155 156 // #pragma mark - 157 158 159 BArchivable* 160 BListView::Instantiate(BMessage* archive) 161 { 162 if (validate_instantiation(archive, "BListView")) 163 return new BListView(archive); 164 165 return NULL; 166 } 167 168 169 status_t 170 BListView::Archive(BMessage* data, bool deep) const 171 { 172 status_t status = BView::Archive(data, deep); 173 if (status < B_OK) 174 return status; 175 176 status = data->AddInt32("_lv_type", fListType); 177 if (status == B_OK && deep) { 178 BListItem* item; 179 int32 i = 0; 180 181 while ((item = ItemAt(i++)) != NULL) { 182 BMessage subData; 183 status = item->Archive(&subData, true); 184 if (status >= B_OK) 185 status = data->AddMessage("_l_items", &subData); 186 187 if (status < B_OK) 188 break; 189 } 190 } 191 192 if (status >= B_OK && InvocationMessage() != NULL) 193 status = data->AddMessage("_msg", InvocationMessage()); 194 195 if (status == B_OK && fSelectMessage != NULL) 196 status = data->AddMessage("_2nd_msg", fSelectMessage); 197 198 return status; 199 } 200 201 202 // #pragma mark - 203 204 205 void 206 BListView::Draw(BRect updateRect) 207 { 208 int32 count = CountItems(); 209 if (count == 0) 210 return; 211 212 BRect itemFrame(0, 0, Bounds().right, -1); 213 for (int i = 0; i < count; i++) { 214 BListItem* item = ItemAt(i); 215 itemFrame.bottom = itemFrame.top + ceilf(item->Height()) - 1; 216 217 if (itemFrame.Intersects(updateRect)) 218 DrawItem(item, itemFrame); 219 220 itemFrame.top = itemFrame.bottom + 1; 221 } 222 } 223 224 225 void 226 BListView::AttachedToWindow() 227 { 228 BView::AttachedToWindow(); 229 _UpdateItems(); 230 231 if (!Messenger().IsValid()) 232 SetTarget(Window(), NULL); 233 234 _FixupScrollBar(); 235 } 236 237 238 void 239 BListView::DetachedFromWindow() 240 { 241 BView::DetachedFromWindow(); 242 } 243 244 245 void 246 BListView::AllAttached() 247 { 248 BView::AllAttached(); 249 } 250 251 252 void 253 BListView::AllDetached() 254 { 255 BView::AllDetached(); 256 } 257 258 259 void 260 BListView::FrameResized(float newWidth, float newHeight) 261 { 262 _FixupScrollBar(); 263 264 // notify items of new width. 265 _UpdateItems(); 266 } 267 268 269 void 270 BListView::FrameMoved(BPoint newPosition) 271 { 272 BView::FrameMoved(newPosition); 273 } 274 275 276 void 277 BListView::TargetedByScrollView(BScrollView* view) 278 { 279 fScrollView = view; 280 // TODO: We could SetFlags(Flags() | B_FRAME_EVENTS) here, but that 281 // may mess up application code which manages this by some other means 282 // and doesn't want us to be messing with flags. 283 } 284 285 286 void 287 BListView::WindowActivated(bool active) 288 { 289 BView::WindowActivated(active); 290 } 291 292 293 // #pragma mark - 294 295 296 void 297 BListView::MessageReceived(BMessage* message) 298 { 299 switch (message->what) { 300 case B_MOUSE_WHEEL_CHANGED: 301 if (!fTrack->is_dragging) 302 BView::MessageReceived(message); 303 break; 304 305 case B_COUNT_PROPERTIES: 306 case B_EXECUTE_PROPERTY: 307 case B_GET_PROPERTY: 308 case B_SET_PROPERTY: 309 { 310 BPropertyInfo propInfo(sProperties); 311 BMessage specifier; 312 const char* property; 313 314 if (message->GetCurrentSpecifier(NULL, &specifier) != B_OK 315 || specifier.FindString("property", &property) != B_OK) { 316 return; 317 } 318 319 switch (propInfo.FindMatch(message, 0, &specifier, message->what, 320 property)) { 321 case B_ERROR: 322 BView::MessageReceived(message); 323 break; 324 325 case 0: 326 { 327 BMessage reply(B_REPLY); 328 reply.AddInt32("result", CountItems()); 329 reply.AddInt32("error", B_OK); 330 331 message->SendReply(&reply); 332 break; 333 } 334 335 case 1: 336 break; 337 338 case 2: 339 { 340 int32 count = 0; 341 342 for (int32 i = 0; i < CountItems(); i++) { 343 if (ItemAt(i)->IsSelected()) 344 count++; 345 } 346 347 BMessage reply(B_REPLY); 348 reply.AddInt32("result", count); 349 reply.AddInt32("error", B_OK); 350 351 message->SendReply(&reply); 352 break; 353 } 354 355 case 3: 356 break; 357 358 case 4: 359 { 360 BMessage reply (B_REPLY); 361 362 for (int32 i = 0; i < CountItems(); i++) { 363 if (ItemAt(i)->IsSelected()) 364 reply.AddInt32("result", i); 365 } 366 367 reply.AddInt32("error", B_OK); 368 369 message->SendReply(&reply); 370 break; 371 } 372 373 case 5: 374 break; 375 376 case 6: 377 { 378 BMessage reply(B_REPLY); 379 380 bool select; 381 if (message->FindBool("data", &select) == B_OK && select) 382 Select(0, CountItems() - 1, false); 383 else 384 DeselectAll(); 385 386 reply.AddInt32("error", B_OK); 387 388 message->SendReply(&reply); 389 break; 390 } 391 } 392 break; 393 } 394 395 case B_SELECT_ALL: 396 if (fListType == B_MULTIPLE_SELECTION_LIST) 397 Select(0, CountItems() - 1, false); 398 break; 399 400 default: 401 BView::MessageReceived(message); 402 } 403 } 404 405 406 void 407 BListView::KeyDown(const char* bytes, int32 numBytes) 408 { 409 bool extend = fListType == B_MULTIPLE_SELECTION_LIST 410 && (modifiers() & B_SHIFT_KEY) != 0; 411 412 if (fFirstSelected == -1 413 && (bytes[0] == B_UP_ARROW || bytes[0] == B_DOWN_ARROW)) { 414 // nothing is selected yet, select the first enabled item 415 int32 lastItem = CountItems() - 1; 416 for (int32 i = 0; i <= lastItem; i++) { 417 if (ItemAt(i)->IsEnabled()) { 418 Select(i); 419 break; 420 } 421 } 422 return; 423 } 424 425 switch (bytes[0]) { 426 case B_UP_ARROW: 427 { 428 if (fAnchorIndex > 0) { 429 if (!extend || fAnchorIndex <= fFirstSelected) { 430 for (int32 i = 1; fAnchorIndex - i >= 0; i++) { 431 if (ItemAt(fAnchorIndex - i)->IsEnabled()) { 432 // Select the previous enabled item 433 Select(fAnchorIndex - i, extend); 434 break; 435 } 436 } 437 } else { 438 Deselect(fAnchorIndex); 439 do 440 fAnchorIndex--; 441 while (fAnchorIndex > 0 442 && !ItemAt(fAnchorIndex)->IsEnabled()); 443 } 444 } 445 446 ScrollToSelection(); 447 break; 448 } 449 450 case B_DOWN_ARROW: 451 { 452 int32 lastItem = CountItems() - 1; 453 if (fAnchorIndex < lastItem) { 454 if (!extend || fAnchorIndex >= fLastSelected) { 455 for (int32 i = 1; fAnchorIndex + i <= lastItem; i++) { 456 if (ItemAt(fAnchorIndex + i)->IsEnabled()) { 457 // Select the next enabled item 458 Select(fAnchorIndex + i, extend); 459 break; 460 } 461 } 462 } else { 463 Deselect(fAnchorIndex); 464 do 465 fAnchorIndex++; 466 while (fAnchorIndex < lastItem 467 && !ItemAt(fAnchorIndex)->IsEnabled()); 468 } 469 } 470 471 ScrollToSelection(); 472 break; 473 } 474 475 case B_HOME: 476 if (extend) { 477 Select(0, fAnchorIndex, true); 478 fAnchorIndex = 0; 479 } else { 480 // select the first enabled item 481 int32 lastItem = CountItems() - 1; 482 for (int32 i = 0; i <= lastItem; i++) { 483 if (ItemAt(i)->IsEnabled()) { 484 Select(i, false); 485 break; 486 } 487 } 488 } 489 490 ScrollToSelection(); 491 break; 492 493 case B_END: 494 if (extend) { 495 Select(fAnchorIndex, CountItems() - 1, true); 496 fAnchorIndex = CountItems() - 1; 497 } else { 498 // select the last enabled item 499 for (int32 i = CountItems() - 1; i >= 0; i--) { 500 if (ItemAt(i)->IsEnabled()) { 501 Select(i, false); 502 break; 503 } 504 } 505 } 506 507 ScrollToSelection(); 508 break; 509 510 case B_PAGE_UP: 511 { 512 BPoint scrollOffset(LeftTop()); 513 scrollOffset.y = std::max(0.0f, scrollOffset.y - Bounds().Height()); 514 ScrollTo(scrollOffset); 515 break; 516 } 517 518 case B_PAGE_DOWN: 519 { 520 BPoint scrollOffset(LeftTop()); 521 if (BListItem* item = LastItem()) { 522 scrollOffset.y += Bounds().Height(); 523 scrollOffset.y = std::min(item->Bottom() - Bounds().Height(), 524 scrollOffset.y); 525 } 526 ScrollTo(scrollOffset); 527 break; 528 } 529 530 case B_RETURN: 531 case B_SPACE: 532 Invoke(); 533 break; 534 535 default: 536 BView::KeyDown(bytes, numBytes); 537 } 538 } 539 540 541 void 542 BListView::MouseDown(BPoint where) 543 { 544 if (!IsFocus()) { 545 MakeFocus(); 546 Sync(); 547 Window()->UpdateIfNeeded(); 548 } 549 550 int32 index = IndexOf(where); 551 int32 modifiers = 0; 552 553 BMessage* message = Looper()->CurrentMessage(); 554 if (message != NULL) 555 message->FindInt32("modifiers", &modifiers); 556 557 // If the user double (or more) clicked within the current selection, 558 // we don't change the selection but invoke the selection. 559 // TODO: move this code someplace where it can be shared everywhere 560 // instead of every class having to reimplement it, once some sane 561 // API for it is decided. 562 BPoint delta = where - fTrack->drag_start; 563 bigtime_t sysTime; 564 Window()->CurrentMessage()->FindInt64("when", &sysTime); 565 bigtime_t timeDelta = sysTime - fTrack->last_click_time; 566 bigtime_t doubleClickSpeed; 567 get_click_speed(&doubleClickSpeed); 568 bool doubleClick = false; 569 570 if (timeDelta < doubleClickSpeed 571 && fabs(delta.x) < kDoubleClickThreshold 572 && fabs(delta.y) < kDoubleClickThreshold 573 && fTrack->item_index == index) { 574 doubleClick = true; 575 } 576 577 if (doubleClick && index >= fFirstSelected && index <= fLastSelected) { 578 fTrack->drag_start.Set(INT32_MAX, INT32_MAX); 579 Invoke(); 580 return BView::MouseDown(where); 581 } 582 583 if (!doubleClick) { 584 fTrack->drag_start = where; 585 fTrack->last_click_time = system_time(); 586 fTrack->item_index = index; 587 fTrack->was_selected = index >= 0 ? ItemAt(index)->IsSelected() : false; 588 fTrack->try_drag = true; 589 590 MouseDownThread<BListView>::TrackMouse(this, 591 &BListView::_DoneTracking, &BListView::_Track); 592 } 593 594 if (index >= 0) { 595 if (fListType == B_MULTIPLE_SELECTION_LIST) { 596 if ((modifiers & B_SHIFT_KEY) != 0) { 597 // select entire block 598 if (index >= fFirstSelected && index < fLastSelected) 599 // clicked inside of selected items block, deselect all 600 // but from the first selected item to the clicked item 601 DeselectExcept(fFirstSelected, index); 602 else 603 Select(std::min(index, fFirstSelected), std::max(index, 604 fLastSelected)); 605 } else { 606 if ((modifiers & B_COMMAND_KEY) != 0) { 607 // toggle selection state of clicked item (like in Tracker) 608 if (ItemAt(index)->IsSelected()) 609 Deselect(index); 610 else 611 Select(index, true); 612 } else if (!ItemAt(index)->IsSelected()) 613 // To enable multi-select drag and drop, we only 614 // exclusively select a single item if it's not one of the 615 // already selected items. This behavior gives the mouse 616 // tracking thread the opportunity to initiate the 617 // multi-selection drag with all the items still selected. 618 Select(index); 619 } 620 } else { 621 // toggle selection state of clicked item (except drag & drop) 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 } 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