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