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