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