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