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