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