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