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