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 BView::MessageReceived(message); 317 return; 318 } 319 320 switch (propInfo.FindMatch(message, 0, &specifier, message->what, 321 property)) { 322 case B_ERROR: 323 BView::MessageReceived(message); 324 break; 325 326 case 0: 327 { 328 BMessage reply(B_REPLY); 329 reply.AddInt32("result", CountItems()); 330 reply.AddInt32("error", B_OK); 331 332 message->SendReply(&reply); 333 break; 334 } 335 336 case 1: 337 break; 338 339 case 2: 340 { 341 int32 count = 0; 342 343 for (int32 i = 0; i < CountItems(); i++) { 344 if (ItemAt(i)->IsSelected()) 345 count++; 346 } 347 348 BMessage reply(B_REPLY); 349 reply.AddInt32("result", count); 350 reply.AddInt32("error", B_OK); 351 352 message->SendReply(&reply); 353 break; 354 } 355 356 case 3: 357 break; 358 359 case 4: 360 { 361 BMessage reply (B_REPLY); 362 363 for (int32 i = 0; i < CountItems(); i++) { 364 if (ItemAt(i)->IsSelected()) 365 reply.AddInt32("result", i); 366 } 367 368 reply.AddInt32("error", B_OK); 369 370 message->SendReply(&reply); 371 break; 372 } 373 374 case 5: 375 break; 376 377 case 6: 378 { 379 BMessage reply(B_REPLY); 380 381 bool select; 382 if (message->FindBool("data", &select) == B_OK && select) 383 Select(0, CountItems() - 1, false); 384 else 385 DeselectAll(); 386 387 reply.AddInt32("error", B_OK); 388 389 message->SendReply(&reply); 390 break; 391 } 392 } 393 break; 394 } 395 396 case B_SELECT_ALL: 397 if (fListType == B_MULTIPLE_SELECTION_LIST) 398 Select(0, CountItems() - 1, false); 399 break; 400 401 default: 402 BView::MessageReceived(message); 403 } 404 } 405 406 407 void 408 BListView::KeyDown(const char* bytes, int32 numBytes) 409 { 410 bool extend = fListType == B_MULTIPLE_SELECTION_LIST 411 && (modifiers() & B_SHIFT_KEY) != 0; 412 413 if (fFirstSelected == -1 414 && (bytes[0] == B_UP_ARROW || bytes[0] == B_DOWN_ARROW)) { 415 // nothing is selected yet, select the first enabled item 416 int32 lastItem = CountItems() - 1; 417 for (int32 i = 0; i <= lastItem; i++) { 418 if (ItemAt(i)->IsEnabled()) { 419 Select(i); 420 break; 421 } 422 } 423 return; 424 } 425 426 switch (bytes[0]) { 427 case B_UP_ARROW: 428 { 429 if (fAnchorIndex > 0) { 430 if (!extend || fAnchorIndex <= fFirstSelected) { 431 for (int32 i = 1; fAnchorIndex - i >= 0; i++) { 432 if (ItemAt(fAnchorIndex - i)->IsEnabled()) { 433 // Select the previous enabled item 434 Select(fAnchorIndex - i, extend); 435 break; 436 } 437 } 438 } else { 439 Deselect(fAnchorIndex); 440 do 441 fAnchorIndex--; 442 while (fAnchorIndex > 0 443 && !ItemAt(fAnchorIndex)->IsEnabled()); 444 } 445 } 446 447 ScrollToSelection(); 448 break; 449 } 450 451 case B_DOWN_ARROW: 452 { 453 int32 lastItem = CountItems() - 1; 454 if (fAnchorIndex < lastItem) { 455 if (!extend || fAnchorIndex >= fLastSelected) { 456 for (int32 i = 1; fAnchorIndex + i <= lastItem; i++) { 457 if (ItemAt(fAnchorIndex + i)->IsEnabled()) { 458 // Select the next enabled item 459 Select(fAnchorIndex + i, extend); 460 break; 461 } 462 } 463 } else { 464 Deselect(fAnchorIndex); 465 do 466 fAnchorIndex++; 467 while (fAnchorIndex < lastItem 468 && !ItemAt(fAnchorIndex)->IsEnabled()); 469 } 470 } 471 472 ScrollToSelection(); 473 break; 474 } 475 476 case B_HOME: 477 if (extend) { 478 Select(0, fAnchorIndex, true); 479 fAnchorIndex = 0; 480 } else { 481 // select the first enabled item 482 int32 lastItem = CountItems() - 1; 483 for (int32 i = 0; i <= lastItem; i++) { 484 if (ItemAt(i)->IsEnabled()) { 485 Select(i, false); 486 break; 487 } 488 } 489 } 490 491 ScrollToSelection(); 492 break; 493 494 case B_END: 495 if (extend) { 496 Select(fAnchorIndex, CountItems() - 1, true); 497 fAnchorIndex = CountItems() - 1; 498 } else { 499 // select the last enabled item 500 for (int32 i = CountItems() - 1; i >= 0; i--) { 501 if (ItemAt(i)->IsEnabled()) { 502 Select(i, false); 503 break; 504 } 505 } 506 } 507 508 ScrollToSelection(); 509 break; 510 511 case B_PAGE_UP: 512 { 513 BPoint scrollOffset(LeftTop()); 514 scrollOffset.y = std::max(0.0f, scrollOffset.y - Bounds().Height()); 515 ScrollTo(scrollOffset); 516 break; 517 } 518 519 case B_PAGE_DOWN: 520 { 521 BPoint scrollOffset(LeftTop()); 522 if (BListItem* item = LastItem()) { 523 scrollOffset.y += Bounds().Height(); 524 scrollOffset.y = std::min(item->Bottom() - Bounds().Height(), 525 scrollOffset.y); 526 } 527 ScrollTo(scrollOffset); 528 break; 529 } 530 531 case B_RETURN: 532 case B_SPACE: 533 Invoke(); 534 break; 535 536 default: 537 BView::KeyDown(bytes, numBytes); 538 } 539 } 540 541 542 void 543 BListView::MouseDown(BPoint where) 544 { 545 if (!IsFocus()) { 546 MakeFocus(); 547 Sync(); 548 Window()->UpdateIfNeeded(); 549 } 550 551 int32 index = IndexOf(where); 552 int32 modifiers = 0; 553 554 BMessage* message = Looper()->CurrentMessage(); 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 if (index >= fFirstSelected && index < fLastSelected) 600 // clicked inside of selected items block, deselect all 601 // but from the first selected item to the clicked item 602 DeselectExcept(fFirstSelected, index); 603 else 604 Select(std::min(index, fFirstSelected), std::max(index, 605 fLastSelected)); 606 } else { 607 if ((modifiers & B_COMMAND_KEY) != 0) { 608 // toggle selection state of clicked item (like in Tracker) 609 if (ItemAt(index)->IsSelected()) 610 Deselect(index); 611 else 612 Select(index, true); 613 } else if (!ItemAt(index)->IsSelected()) 614 // To enable multi-select drag and drop, we only 615 // exclusively select a single item if it's not one of the 616 // already selected items. This behavior gives the mouse 617 // tracking thread the opportunity to initiate the 618 // multi-selection drag with all the items still selected. 619 Select(index); 620 } 621 } else { 622 // toggle selection state of clicked item (except drag & drop) 623 if ((modifiers & B_COMMAND_KEY) != 0 && ItemAt(index)->IsSelected()) 624 Deselect(index); 625 else 626 Select(index); 627 } 628 } else if ((modifiers & B_COMMAND_KEY) == 0) 629 DeselectAll(); 630 631 BView::MouseDown(where); 632 } 633 634 635 void 636 BListView::MouseUp(BPoint where) 637 { 638 BView::MouseUp(where); 639 } 640 641 642 void 643 BListView::MouseMoved(BPoint where, uint32 code, const BMessage* dragMessage) 644 { 645 BView::MouseMoved(where, code, dragMessage); 646 } 647 648 649 bool 650 BListView::InitiateDrag(BPoint where, int32 index, bool wasSelected) 651 { 652 return false; 653 } 654 655 656 // #pragma mark - 657 658 659 void 660 BListView::ResizeToPreferred() 661 { 662 BView::ResizeToPreferred(); 663 } 664 665 666 void 667 BListView::GetPreferredSize(float *_width, float *_height) 668 { 669 int32 count = CountItems(); 670 671 if (count > 0) { 672 float maxWidth = 0.0; 673 for (int32 i = 0; i < count; i++) { 674 float itemWidth = ItemAt(i)->Width(); 675 if (itemWidth > maxWidth) 676 maxWidth = itemWidth; 677 } 678 679 if (_width != NULL) 680 *_width = maxWidth; 681 if (_height != NULL) 682 *_height = ItemAt(count - 1)->Bottom(); 683 } else 684 BView::GetPreferredSize(_width, _height); 685 } 686 687 688 BSize 689 BListView::MinSize() 690 { 691 // We need a stable min size: the BView implementation uses 692 // GetPreferredSize(), which by default just returns the current size. 693 return BLayoutUtils::ComposeSize(ExplicitMinSize(), BSize(10, 10)); 694 } 695 696 697 BSize 698 BListView::MaxSize() 699 { 700 return BView::MaxSize(); 701 } 702 703 704 BSize 705 BListView::PreferredSize() 706 { 707 // We need a stable preferred size: the BView implementation uses 708 // GetPreferredSize(), which by default just returns the current size. 709 return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), BSize(100, 50)); 710 } 711 712 713 // #pragma mark - 714 715 716 void 717 BListView::MakeFocus(bool focused) 718 { 719 if (IsFocus() == focused) 720 return; 721 722 BView::MakeFocus(focused); 723 724 if (fScrollView) 725 fScrollView->SetBorderHighlighted(focused); 726 } 727 728 729 void 730 BListView::SetFont(const BFont* font, uint32 mask) 731 { 732 BView::SetFont(font, mask); 733 734 if (Window() != NULL && !Window()->InViewTransaction()) 735 _UpdateItems(); 736 } 737 738 739 void 740 BListView::ScrollTo(BPoint point) 741 { 742 BView::ScrollTo(point); 743 } 744 745 746 // #pragma mark - List ops 747 748 749 bool 750 BListView::AddItem(BListItem* item, int32 index) 751 { 752 if (!fList.AddItem(item, index)) 753 return false; 754 755 if (fFirstSelected != -1 && index <= fFirstSelected) 756 fFirstSelected++; 757 758 if (fLastSelected != -1 && index <= fLastSelected) 759 fLastSelected++; 760 761 if (Window()) { 762 BFont font; 763 GetFont(&font); 764 item->SetTop((index > 0) ? ItemAt(index - 1)->Bottom() + 1.0 : 0.0); 765 766 item->Update(this, &font); 767 _RecalcItemTops(index + 1); 768 769 _FixupScrollBar(); 770 _InvalidateFrom(index); 771 } 772 773 return true; 774 } 775 776 777 bool 778 BListView::AddItem(BListItem* item) 779 { 780 if (!fList.AddItem(item)) 781 return false; 782 783 // No need to adapt selection, as this item is the last in the list 784 785 if (Window()) { 786 BFont font; 787 GetFont(&font); 788 int32 index = CountItems() - 1; 789 item->SetTop((index > 0) ? ItemAt(index - 1)->Bottom() + 1.0 : 0.0); 790 791 item->Update(this, &font); 792 793 _FixupScrollBar(); 794 InvalidateItem(CountItems() - 1); 795 } 796 797 return true; 798 } 799 800 801 bool 802 BListView::AddList(BList* list, int32 index) 803 { 804 if (!fList.AddList(list, index)) 805 return false; 806 807 int32 count = list->CountItems(); 808 809 if (fFirstSelected != -1 && index < fFirstSelected) 810 fFirstSelected += count; 811 812 if (fLastSelected != -1 && index < fLastSelected) 813 fLastSelected += count; 814 815 if (Window()) { 816 BFont font; 817 GetFont(&font); 818 819 for (int32 i = index; i <= (index + count - 1); i++) { 820 ItemAt(i)->SetTop((i > 0) ? ItemAt(i - 1)->Bottom() + 1.0 : 0.0); 821 ItemAt(i)->Update(this, &font); 822 } 823 824 _RecalcItemTops(index + count - 1); 825 826 _FixupScrollBar(); 827 Invalidate(); // TODO 828 } 829 830 return true; 831 } 832 833 834 bool 835 BListView::AddList(BList* list) 836 { 837 return AddList(list, CountItems()); 838 } 839 840 841 BListItem* 842 BListView::RemoveItem(int32 index) 843 { 844 BListItem* item = ItemAt(index); 845 if (item == NULL) 846 return NULL; 847 848 if (item->IsSelected()) 849 Deselect(index); 850 851 if (!fList.RemoveItem(item)) 852 return NULL; 853 854 if (fFirstSelected != -1 && index < fFirstSelected) 855 fFirstSelected--; 856 857 if (fLastSelected != -1 && index < fLastSelected) 858 fLastSelected--; 859 860 if (fAnchorIndex != -1 && index < fAnchorIndex) 861 fAnchorIndex--; 862 863 _RecalcItemTops(index); 864 865 _InvalidateFrom(index); 866 _FixupScrollBar(); 867 868 return item; 869 } 870 871 872 bool 873 BListView::RemoveItem(BListItem* item) 874 { 875 return BListView::RemoveItem(IndexOf(item)) != NULL; 876 } 877 878 879 bool 880 BListView::RemoveItems(int32 index, int32 count) 881 { 882 if (index >= fList.CountItems()) 883 index = -1; 884 885 if (index < 0) 886 return false; 887 888 if (fAnchorIndex != -1 && index < fAnchorIndex) 889 fAnchorIndex = index; 890 891 fList.RemoveItems(index, count); 892 if (index < fList.CountItems()) 893 _RecalcItemTops(index); 894 895 Invalidate(); 896 return true; 897 } 898 899 900 void 901 BListView::SetSelectionMessage(BMessage* message) 902 { 903 delete fSelectMessage; 904 fSelectMessage = message; 905 } 906 907 908 void 909 BListView::SetInvocationMessage(BMessage* message) 910 { 911 BInvoker::SetMessage(message); 912 } 913 914 915 BMessage* 916 BListView::InvocationMessage() const 917 { 918 return BInvoker::Message(); 919 } 920 921 922 uint32 923 BListView::InvocationCommand() const 924 { 925 return BInvoker::Command(); 926 } 927 928 929 BMessage* 930 BListView::SelectionMessage() const 931 { 932 return fSelectMessage; 933 } 934 935 936 uint32 937 BListView::SelectionCommand() const 938 { 939 if (fSelectMessage) 940 return fSelectMessage->what; 941 942 return 0; 943 } 944 945 946 void 947 BListView::SetListType(list_view_type type) 948 { 949 if (fListType == B_MULTIPLE_SELECTION_LIST 950 && type == B_SINGLE_SELECTION_LIST) { 951 Select(CurrentSelection(0)); 952 } 953 954 fListType = type; 955 } 956 957 958 list_view_type 959 BListView::ListType() const 960 { 961 return fListType; 962 } 963 964 965 BListItem* 966 BListView::ItemAt(int32 index) const 967 { 968 return (BListItem*)fList.ItemAt(index); 969 } 970 971 972 int32 973 BListView::IndexOf(BListItem* item) const 974 { 975 if (Window()) { 976 if (item != NULL) { 977 int32 index = IndexOf(BPoint(0.0, item->Top())); 978 if (index >= 0 && fList.ItemAt(index) == item) 979 return index; 980 981 return -1; 982 } 983 } 984 return fList.IndexOf(item); 985 } 986 987 988 int32 989 BListView::IndexOf(BPoint point) const 990 { 991 int32 low = 0; 992 int32 high = fList.CountItems() - 1; 993 int32 mid = -1; 994 float frameTop = -1.0; 995 float frameBottom = 1.0; 996 997 // binary search the list 998 while (high >= low) { 999 mid = (low + high) / 2; 1000 frameTop = ItemAt(mid)->Top(); 1001 frameBottom = ItemAt(mid)->Bottom(); 1002 if (point.y < frameTop) 1003 high = mid - 1; 1004 else if (point.y > frameBottom) 1005 low = mid + 1; 1006 else 1007 return mid; 1008 } 1009 1010 return -1; 1011 } 1012 1013 1014 BListItem* 1015 BListView::FirstItem() const 1016 { 1017 return (BListItem*)fList.FirstItem(); 1018 } 1019 1020 1021 BListItem* 1022 BListView::LastItem() const 1023 { 1024 return (BListItem*)fList.LastItem(); 1025 } 1026 1027 1028 bool 1029 BListView::HasItem(BListItem *item) const 1030 { 1031 return IndexOf(item) != -1; 1032 } 1033 1034 1035 int32 1036 BListView::CountItems() const 1037 { 1038 return fList.CountItems(); 1039 } 1040 1041 1042 void 1043 BListView::MakeEmpty() 1044 { 1045 if (fList.IsEmpty()) 1046 return; 1047 1048 _DeselectAll(-1, -1); 1049 fList.MakeEmpty(); 1050 1051 if (Window()) { 1052 _FixupScrollBar(); 1053 Invalidate(); 1054 } 1055 } 1056 1057 1058 bool 1059 BListView::IsEmpty() const 1060 { 1061 return fList.IsEmpty(); 1062 } 1063 1064 1065 void 1066 BListView::DoForEach(bool (*func)(BListItem*)) 1067 { 1068 fList.DoForEach(reinterpret_cast<bool (*)(void*)>(func)); 1069 } 1070 1071 1072 void 1073 BListView::DoForEach(bool (*func)(BListItem*, void*), void* arg) 1074 { 1075 fList.DoForEach(reinterpret_cast<bool (*)(void*, void*)>(func), arg); 1076 } 1077 1078 1079 const BListItem** 1080 BListView::Items() const 1081 { 1082 return (const BListItem**)fList.Items(); 1083 } 1084 1085 1086 void 1087 BListView::InvalidateItem(int32 index) 1088 { 1089 Invalidate(ItemFrame(index)); 1090 } 1091 1092 1093 void 1094 BListView::ScrollToSelection() 1095 { 1096 BRect itemFrame = ItemFrame(CurrentSelection(0)); 1097 1098 if (Bounds().Contains(itemFrame)) 1099 return; 1100 1101 float scrollPos = itemFrame.top < Bounds().top ? 1102 itemFrame.top : itemFrame.bottom - Bounds().Height(); 1103 1104 if (itemFrame.top - scrollPos < Bounds().top) 1105 scrollPos = itemFrame.top; 1106 1107 ScrollTo(itemFrame.left, scrollPos); 1108 } 1109 1110 1111 void 1112 BListView::Select(int32 index, bool extend) 1113 { 1114 if (_Select(index, extend)) { 1115 SelectionChanged(); 1116 InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED); 1117 } 1118 } 1119 1120 1121 void 1122 BListView::Select(int32 start, int32 finish, bool extend) 1123 { 1124 if (_Select(start, finish, extend)) { 1125 SelectionChanged(); 1126 InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED); 1127 } 1128 } 1129 1130 1131 bool 1132 BListView::IsItemSelected(int32 index) const 1133 { 1134 BListItem* item = ItemAt(index); 1135 if (item != NULL) 1136 return item->IsSelected(); 1137 1138 return false; 1139 } 1140 1141 1142 int32 1143 BListView::CurrentSelection(int32 index) const 1144 { 1145 if (fFirstSelected == -1) 1146 return -1; 1147 1148 if (index == 0) 1149 return fFirstSelected; 1150 1151 for (int32 i = fFirstSelected; i <= fLastSelected; i++) { 1152 if (ItemAt(i)->IsSelected()) { 1153 if (index == 0) 1154 return i; 1155 1156 index--; 1157 } 1158 } 1159 1160 return -1; 1161 } 1162 1163 1164 status_t 1165 BListView::Invoke(BMessage* message) 1166 { 1167 // Note, this is more or less a copy of BControl::Invoke() and should 1168 // stay that way (ie. changes done there should be adopted here) 1169 1170 bool notify = false; 1171 uint32 kind = InvokeKind(¬ify); 1172 1173 BMessage clone(kind); 1174 status_t err = B_BAD_VALUE; 1175 1176 if (!message && !notify) 1177 message = Message(); 1178 1179 if (!message) { 1180 if (!IsWatched()) 1181 return err; 1182 } else 1183 clone = *message; 1184 1185 clone.AddInt64("when", (int64)system_time()); 1186 clone.AddPointer("source", this); 1187 clone.AddMessenger("be:sender", BMessenger(this)); 1188 1189 if (fListType == B_SINGLE_SELECTION_LIST) 1190 clone.AddInt32("index", fFirstSelected); 1191 else { 1192 if (fFirstSelected >= 0) { 1193 for (int32 i = fFirstSelected; i <= fLastSelected; i++) { 1194 if (ItemAt(i)->IsSelected()) 1195 clone.AddInt32("index", i); 1196 } 1197 } 1198 } 1199 1200 if (message) 1201 err = BInvoker::Invoke(&clone); 1202 1203 SendNotices(kind, &clone); 1204 1205 return err; 1206 } 1207 1208 1209 void 1210 BListView::DeselectAll() 1211 { 1212 if (_DeselectAll(-1, -1)) { 1213 SelectionChanged(); 1214 InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED); 1215 } 1216 } 1217 1218 1219 void 1220 BListView::DeselectExcept(int32 exceptFrom, int32 exceptTo) 1221 { 1222 if (exceptFrom > exceptTo || exceptFrom < 0 || exceptTo < 0) 1223 return; 1224 1225 if (_DeselectAll(exceptFrom, exceptTo)) { 1226 SelectionChanged(); 1227 InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED); 1228 } 1229 } 1230 1231 1232 void 1233 BListView::Deselect(int32 index) 1234 { 1235 if (_Deselect(index)) { 1236 SelectionChanged(); 1237 InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED); 1238 } 1239 } 1240 1241 1242 void 1243 BListView::SelectionChanged() 1244 { 1245 // Hook method to be implemented by subclasses 1246 } 1247 1248 1249 void 1250 BListView::SortItems(int (*cmp)(const void *, const void *)) 1251 { 1252 if (_DeselectAll(-1, -1)) { 1253 SelectionChanged(); 1254 InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED); 1255 } 1256 1257 fList.SortItems(cmp); 1258 _RecalcItemTops(0); 1259 Invalidate(); 1260 } 1261 1262 1263 bool 1264 BListView::SwapItems(int32 a, int32 b) 1265 { 1266 MiscData data; 1267 1268 data.swap.a = a; 1269 data.swap.b = b; 1270 1271 return DoMiscellaneous(B_SWAP_OP, &data); 1272 } 1273 1274 1275 bool 1276 BListView::MoveItem(int32 from, int32 to) 1277 { 1278 MiscData data; 1279 1280 data.move.from = from; 1281 data.move.to = to; 1282 1283 return DoMiscellaneous(B_MOVE_OP, &data); 1284 } 1285 1286 1287 bool 1288 BListView::ReplaceItem(int32 index, BListItem* item) 1289 { 1290 MiscData data; 1291 1292 data.replace.index = index; 1293 data.replace.item = item; 1294 1295 return DoMiscellaneous(B_REPLACE_OP, &data); 1296 } 1297 1298 1299 BRect 1300 BListView::ItemFrame(int32 index) 1301 { 1302 BRect frame = Bounds(); 1303 if (index < 0 || index >= CountItems()) { 1304 frame.top = 0; 1305 frame.bottom = -1; 1306 } else { 1307 BListItem* item = ItemAt(index); 1308 frame.top = item->Top(); 1309 frame.bottom = item->Bottom(); 1310 } 1311 return frame; 1312 } 1313 1314 1315 // #pragma mark - 1316 1317 1318 BHandler* 1319 BListView::ResolveSpecifier(BMessage* message, int32 index, 1320 BMessage* specifier, int32 what, const char* property) 1321 { 1322 BPropertyInfo propInfo(sProperties); 1323 1324 if (propInfo.FindMatch(message, 0, specifier, what, property) < 0) { 1325 return BView::ResolveSpecifier(message, index, specifier, what, 1326 property); 1327 } 1328 1329 // TODO: msg->AddInt32("_match_code_", ); 1330 1331 return this; 1332 } 1333 1334 1335 status_t 1336 BListView::GetSupportedSuites(BMessage* data) 1337 { 1338 if (data == NULL) 1339 return B_BAD_VALUE; 1340 1341 status_t err = data->AddString("suites", "suite/vnd.Be-list-view"); 1342 1343 BPropertyInfo propertyInfo(sProperties); 1344 if (err == B_OK) 1345 err = data->AddFlat("messages", &propertyInfo); 1346 1347 if (err == B_OK) 1348 return BView::GetSupportedSuites(data); 1349 return err; 1350 } 1351 1352 1353 status_t 1354 BListView::Perform(perform_code code, void* _data) 1355 { 1356 switch (code) { 1357 case PERFORM_CODE_MIN_SIZE: 1358 ((perform_data_min_size*)_data)->return_value 1359 = BListView::MinSize(); 1360 return B_OK; 1361 case PERFORM_CODE_MAX_SIZE: 1362 ((perform_data_max_size*)_data)->return_value 1363 = BListView::MaxSize(); 1364 return B_OK; 1365 case PERFORM_CODE_PREFERRED_SIZE: 1366 ((perform_data_preferred_size*)_data)->return_value 1367 = BListView::PreferredSize(); 1368 return B_OK; 1369 case PERFORM_CODE_LAYOUT_ALIGNMENT: 1370 ((perform_data_layout_alignment*)_data)->return_value 1371 = BListView::LayoutAlignment(); 1372 return B_OK; 1373 case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH: 1374 ((perform_data_has_height_for_width*)_data)->return_value 1375 = BListView::HasHeightForWidth(); 1376 return B_OK; 1377 case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH: 1378 { 1379 perform_data_get_height_for_width* data 1380 = (perform_data_get_height_for_width*)_data; 1381 BListView::GetHeightForWidth(data->width, &data->min, &data->max, 1382 &data->preferred); 1383 return B_OK; 1384 } 1385 case PERFORM_CODE_SET_LAYOUT: 1386 { 1387 perform_data_set_layout* data = (perform_data_set_layout*)_data; 1388 BListView::SetLayout(data->layout); 1389 return B_OK; 1390 } 1391 case PERFORM_CODE_LAYOUT_INVALIDATED: 1392 { 1393 perform_data_layout_invalidated* data 1394 = (perform_data_layout_invalidated*)_data; 1395 BListView::LayoutInvalidated(data->descendants); 1396 return B_OK; 1397 } 1398 case PERFORM_CODE_DO_LAYOUT: 1399 { 1400 BListView::DoLayout(); 1401 return B_OK; 1402 } 1403 } 1404 1405 return BView::Perform(code, _data); 1406 } 1407 1408 1409 bool 1410 BListView::DoMiscellaneous(MiscCode code, MiscData* data) 1411 { 1412 if (code > B_SWAP_OP) 1413 return false; 1414 1415 switch (code) { 1416 case B_NO_OP: 1417 break; 1418 1419 case B_REPLACE_OP: 1420 return _ReplaceItem(data->replace.index, data->replace.item); 1421 1422 case B_MOVE_OP: 1423 return _MoveItem(data->move.from, data->move.to); 1424 1425 case B_SWAP_OP: 1426 return _SwapItems(data->swap.a, data->swap.b); 1427 } 1428 1429 return false; 1430 } 1431 1432 1433 // #pragma mark - 1434 1435 1436 void BListView::_ReservedListView2() {} 1437 void BListView::_ReservedListView3() {} 1438 void BListView::_ReservedListView4() {} 1439 1440 1441 BListView& 1442 BListView::operator=(const BListView& /*other*/) 1443 { 1444 return *this; 1445 } 1446 1447 1448 // #pragma mark - 1449 1450 1451 void 1452 BListView::_InitObject(list_view_type type) 1453 { 1454 fListType = type; 1455 fFirstSelected = -1; 1456 fLastSelected = -1; 1457 fAnchorIndex = -1; 1458 fSelectMessage = NULL; 1459 fScrollView = NULL; 1460 1461 fTrack = new track_data; 1462 fTrack->drag_start = B_ORIGIN; 1463 fTrack->item_index = -1; 1464 fTrack->was_selected = false; 1465 fTrack->try_drag = false; 1466 fTrack->is_dragging = false; 1467 fTrack->last_click_time = 0; 1468 1469 SetViewUIColor(B_LIST_BACKGROUND_COLOR); 1470 SetLowUIColor(B_LIST_BACKGROUND_COLOR); 1471 } 1472 1473 1474 void 1475 BListView::_FixupScrollBar() 1476 { 1477 1478 BScrollBar* vertScroller = ScrollBar(B_VERTICAL); 1479 if (vertScroller != NULL) { 1480 BRect bounds = Bounds(); 1481 int32 count = CountItems(); 1482 1483 float itemHeight = 0.0; 1484 1485 if (CountItems() > 0) 1486 itemHeight = ItemAt(CountItems() - 1)->Bottom(); 1487 1488 if (bounds.Height() > itemHeight) { 1489 // no scrolling 1490 vertScroller->SetRange(0.0, 0.0); 1491 vertScroller->SetValue(0.0); 1492 // also scrolls ListView to the top 1493 } else { 1494 vertScroller->SetRange(0.0, itemHeight - bounds.Height() - 1.0); 1495 vertScroller->SetProportion(bounds.Height () / itemHeight); 1496 // scroll up if there is empty room on bottom 1497 if (itemHeight < bounds.bottom) 1498 ScrollBy(0.0, bounds.bottom - itemHeight); 1499 } 1500 1501 if (count != 0) 1502 vertScroller->SetSteps( 1503 ceilf(FirstItem()->Height()), bounds.Height()); 1504 } 1505 1506 BScrollBar* horizontalScroller = ScrollBar(B_HORIZONTAL); 1507 if (horizontalScroller != NULL) { 1508 float w; 1509 GetPreferredSize(&w, NULL); 1510 BRect scrollBarSize = horizontalScroller->Bounds(); 1511 1512 if (w <= scrollBarSize.Width()) { 1513 // no scrolling 1514 horizontalScroller->SetRange(0.0, 0.0); 1515 horizontalScroller->SetValue(0.0); 1516 } else { 1517 horizontalScroller->SetRange(0, w - scrollBarSize.Width()); 1518 horizontalScroller->SetProportion(scrollBarSize.Width() / w); 1519 } 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