1 /* 2 * Copyright 2001-2013 Haiku, Inc. 3 * Distributed under the terms of the MIT License. 4 * 5 * Authors: 6 * Marc Flerackers (mflerackers@androme.be) 7 * Axel Dörfler, axeld@pinc-software.de 8 * Rene Gollent (rene@gollent.com) 9 * Philippe Saint-Pierre, stpere@gmail.com 10 * John Scipione, jscipione@gmail.com 11 */ 12 13 //! BOutlineListView represents a "nestable" list view. 14 15 #include <OutlineListView.h> 16 17 #include <algorithm> 18 19 #include <stdio.h> 20 #include <stdlib.h> 21 22 #include <ControlLook.h> 23 #include <Window.h> 24 25 #include <binary_compatibility/Interface.h> 26 27 28 typedef int (*compare_func)(const BListItem* a, const BListItem* b); 29 30 struct ListItemComparator { 31 ListItemComparator(compare_func compareFunc) 32 : 33 fCompareFunc(compareFunc) 34 { 35 } 36 37 bool operator()(const BListItem* a, const BListItem* b) const 38 { 39 return fCompareFunc(a, b) < 0; 40 } 41 42 private: 43 compare_func fCompareFunc; 44 }; 45 46 47 static void 48 _GetSubItems(BList& sourceList, BList& destList, BListItem* parent, int32 start) 49 { 50 for (int32 i = start; i < sourceList.CountItems(); i++) { 51 BListItem* item = (BListItem*)sourceList.ItemAt(i); 52 if (item->OutlineLevel() <= parent->OutlineLevel()) 53 break; 54 destList.AddItem(item); 55 } 56 } 57 58 59 static void 60 _DoSwap(BList& list, int32 firstIndex, int32 secondIndex, BList* firstItems, 61 BList* secondItems) 62 { 63 BListItem* item = (BListItem*)list.ItemAt(firstIndex); 64 list.SwapItems(firstIndex, secondIndex); 65 list.RemoveItems(secondIndex + 1, secondItems->CountItems()); 66 list.RemoveItems(firstIndex + 1, firstItems->CountItems()); 67 list.AddList(secondItems, firstIndex + 1); 68 int32 newIndex = list.IndexOf(item); 69 if (newIndex + 1 < list.CountItems()) 70 list.AddList(firstItems, newIndex + 1); 71 else 72 list.AddList(firstItems); 73 } 74 75 76 // #pragma mark - 77 78 79 BOutlineListView::BOutlineListView(BRect frame, const char* name, 80 list_view_type type, uint32 resizingMode, uint32 flags) 81 : 82 BListView(frame, name, type, resizingMode, flags) 83 { 84 } 85 86 87 BOutlineListView::BOutlineListView(const char* name, list_view_type type, 88 uint32 flags) 89 : 90 BListView(name, type, flags) 91 { 92 } 93 94 95 BOutlineListView::BOutlineListView(BMessage* archive) 96 : 97 BListView(archive) 98 { 99 int32 i = 0; 100 BMessage subData; 101 while (archive->FindMessage("_l_full_items", i++, &subData) == B_OK) { 102 BArchivable* object = instantiate_object(&subData); 103 if (!object) 104 continue; 105 106 BListItem* item = dynamic_cast<BListItem*>(object); 107 if (item) 108 AddItem(item); 109 } 110 } 111 112 113 BOutlineListView::~BOutlineListView() 114 { 115 fFullList.MakeEmpty(); 116 } 117 118 119 BArchivable* 120 BOutlineListView::Instantiate(BMessage* archive) 121 { 122 if (validate_instantiation(archive, "BOutlineListView")) 123 return new BOutlineListView(archive); 124 125 return NULL; 126 } 127 128 129 status_t 130 BOutlineListView::Archive(BMessage* archive, bool deep) const 131 { 132 // Note: We can't call the BListView Archive function here, as we are also 133 // interested in subitems BOutlineListView can have. They are even stored 134 // with a different field name (_l_full_items vs. _l_items). 135 136 status_t status = BView::Archive(archive, deep); 137 if (status != B_OK) 138 return status; 139 140 status = archive->AddInt32("_lv_type", fListType); 141 if (status == B_OK && deep) { 142 int32 i = 0; 143 BListItem* item = NULL; 144 while ((item = static_cast<BListItem*>(fFullList.ItemAt(i++)))) { 145 BMessage subData; 146 status = item->Archive(&subData, true); 147 if (status >= B_OK) 148 status = archive->AddMessage("_l_full_items", &subData); 149 150 if (status < B_OK) 151 break; 152 } 153 } 154 155 if (status >= B_OK && InvocationMessage() != NULL) 156 status = archive->AddMessage("_msg", InvocationMessage()); 157 158 if (status == B_OK && fSelectMessage != NULL) 159 status = archive->AddMessage("_2nd_msg", fSelectMessage); 160 161 return status; 162 } 163 164 165 void 166 BOutlineListView::MouseDown(BPoint where) 167 { 168 MakeFocus(); 169 170 int32 index = IndexOf(where); 171 172 if (index != -1) { 173 BListItem* item = ItemAt(index); 174 175 if (item->fHasSubitems 176 && LatchRect(ItemFrame(index), item->fLevel).Contains(where)) { 177 if (item->IsExpanded()) 178 Collapse(item); 179 else 180 Expand(item); 181 } else 182 BListView::MouseDown(where); 183 } 184 } 185 186 187 void 188 BOutlineListView::KeyDown(const char* bytes, int32 numBytes) 189 { 190 if (numBytes == 1) { 191 int32 currentSel = CurrentSelection(); 192 switch (bytes[0]) { 193 case B_RIGHT_ARROW: 194 { 195 BListItem* item = ItemAt(currentSel); 196 if (item && item->fHasSubitems) { 197 if (!IsExpanded(currentSel)) 198 Expand(item); 199 else 200 Select(currentSel + 1); 201 } 202 return; 203 } 204 205 case B_LEFT_ARROW: 206 { 207 BListItem* item = ItemAt(currentSel); 208 if (item) { 209 if (item->fHasSubitems) 210 Collapse(item); 211 else { 212 item = Superitem(item); 213 if (item) 214 Select(IndexOf(item)); 215 } 216 } 217 return; 218 } 219 } 220 } 221 222 BListView::KeyDown(bytes, numBytes); 223 } 224 225 226 void 227 BOutlineListView::FrameMoved(BPoint newPosition) 228 { 229 BListView::FrameMoved(newPosition); 230 } 231 232 233 void 234 BOutlineListView::FrameResized(float newWidth, float newHeight) 235 { 236 BListView::FrameResized(newWidth, newHeight); 237 } 238 239 240 void 241 BOutlineListView::MouseUp(BPoint where) 242 { 243 BListView::MouseUp(where); 244 } 245 246 247 bool 248 BOutlineListView::AddUnder(BListItem* item, BListItem* superItem) 249 { 250 if (superItem == NULL) 251 return AddItem(item); 252 253 fFullList.AddItem(item, FullListIndexOf(superItem) + 1); 254 255 item->fLevel = superItem->OutlineLevel() + 1; 256 superItem->fHasSubitems = true; 257 258 if (superItem->IsItemVisible() && superItem->IsExpanded()) { 259 item->SetItemVisible(true); 260 261 int32 index = BListView::IndexOf(superItem); 262 263 BListView::AddItem(item, index + 1); 264 Invalidate(LatchRect(ItemFrame(index), superItem->OutlineLevel())); 265 } else 266 item->SetItemVisible(false); 267 268 return true; 269 } 270 271 272 bool 273 BOutlineListView::AddItem(BListItem* item) 274 { 275 return AddItem(item, FullListCountItems()); 276 } 277 278 279 bool 280 BOutlineListView::AddItem(BListItem* item, int32 fullListIndex) 281 { 282 if (fullListIndex < 0) 283 fullListIndex = 0; 284 else if (fullListIndex > FullListCountItems()) 285 fullListIndex = FullListCountItems(); 286 287 if (!fFullList.AddItem(item, fullListIndex)) 288 return false; 289 290 // Check if this item is visible, and if it is, add it to the 291 // other list, too 292 293 if (item->fLevel > 0) { 294 BListItem* super = _SuperitemForIndex(fullListIndex, item->fLevel); 295 if (super == NULL) 296 return true; 297 298 bool hadSubitems = super->fHasSubitems; 299 super->fHasSubitems = true; 300 301 if (!super->IsItemVisible() || !super->IsExpanded()) { 302 item->SetItemVisible(false); 303 return true; 304 } 305 306 if (!hadSubitems) { 307 Invalidate(LatchRect(ItemFrame(IndexOf(super)), 308 super->OutlineLevel())); 309 } 310 } 311 312 int32 listIndex = _FindPreviousVisibleIndex(fullListIndex); 313 314 if (!BListView::AddItem(item, IndexOf(FullListItemAt(listIndex)) + 1)) { 315 // adding didn't work out, we need to remove it from the main list again 316 fFullList.RemoveItem(fullListIndex); 317 return false; 318 } 319 320 return true; 321 } 322 323 324 bool 325 BOutlineListView::AddList(BList* newItems) 326 { 327 return AddList(newItems, FullListCountItems()); 328 } 329 330 331 bool 332 BOutlineListView::AddList(BList* newItems, int32 fullListIndex) 333 { 334 if ((newItems == NULL) || (newItems->CountItems() == 0)) 335 return false; 336 337 for (int32 i = 0; i < newItems->CountItems(); i++) 338 AddItem((BListItem*)newItems->ItemAt(i), fullListIndex + i); 339 340 return true; 341 } 342 343 344 bool 345 BOutlineListView::RemoveItem(BListItem* item) 346 { 347 return _RemoveItem(item, FullListIndexOf(item)) != NULL; 348 } 349 350 351 BListItem* 352 BOutlineListView::RemoveItem(int32 fullListIndex) 353 { 354 return _RemoveItem(FullListItemAt(fullListIndex), fullListIndex); 355 } 356 357 358 bool 359 BOutlineListView::RemoveItems(int32 fullListIndex, int32 count) 360 { 361 if (fullListIndex >= FullListCountItems()) 362 fullListIndex = -1; 363 if (fullListIndex < 0) 364 return false; 365 366 // TODO: very bad for performance!! 367 while (count--) 368 BOutlineListView::RemoveItem(fullListIndex); 369 370 return true; 371 } 372 373 374 BListItem* 375 BOutlineListView::FullListItemAt(int32 fullListIndex) const 376 { 377 return (BListItem*)fFullList.ItemAt(fullListIndex); 378 } 379 380 381 int32 382 BOutlineListView::FullListIndexOf(BPoint where) const 383 { 384 int32 index = BListView::IndexOf(where); 385 386 if (index > 0) 387 index = _FullListIndex(index); 388 389 return index; 390 } 391 392 393 int32 394 BOutlineListView::FullListIndexOf(BListItem* item) const 395 { 396 return fFullList.IndexOf(item); 397 } 398 399 400 BListItem* 401 BOutlineListView::FullListFirstItem() const 402 { 403 return (BListItem*)fFullList.FirstItem(); 404 } 405 406 407 BListItem* 408 BOutlineListView::FullListLastItem() const 409 { 410 return (BListItem*)fFullList.LastItem(); 411 } 412 413 414 bool 415 BOutlineListView::FullListHasItem(BListItem* item) const 416 { 417 return fFullList.HasItem(item); 418 } 419 420 421 int32 422 BOutlineListView::FullListCountItems() const 423 { 424 return fFullList.CountItems(); 425 } 426 427 428 int32 429 BOutlineListView::FullListCurrentSelection(int32 index) const 430 { 431 int32 i = BListView::CurrentSelection(index); 432 433 BListItem* item = BListView::ItemAt(i); 434 if (item) 435 return fFullList.IndexOf(item); 436 437 return -1; 438 } 439 440 441 void 442 BOutlineListView::MakeEmpty() 443 { 444 fFullList.MakeEmpty(); 445 BListView::MakeEmpty(); 446 } 447 448 449 bool 450 BOutlineListView::FullListIsEmpty() const 451 { 452 return fFullList.IsEmpty(); 453 } 454 455 456 void 457 BOutlineListView::FullListDoForEach(bool(*func)(BListItem* item)) 458 { 459 fFullList.DoForEach(reinterpret_cast<bool (*)(void*)>(func)); 460 } 461 462 463 void 464 BOutlineListView::FullListDoForEach(bool (*func)(BListItem* item, void* arg), 465 void* arg) 466 { 467 fFullList.DoForEach(reinterpret_cast<bool (*)(void*, void*)>(func), arg); 468 } 469 470 471 BListItem* 472 BOutlineListView::Superitem(const BListItem* item) 473 { 474 int32 index = FullListIndexOf((BListItem*)item); 475 if (index == -1) 476 return NULL; 477 478 return _SuperitemForIndex(index, item->OutlineLevel()); 479 } 480 481 482 void 483 BOutlineListView::Expand(BListItem* item) 484 { 485 ExpandOrCollapse(item, true); 486 } 487 488 489 void 490 BOutlineListView::Collapse(BListItem* item) 491 { 492 ExpandOrCollapse(item, false); 493 } 494 495 496 bool 497 BOutlineListView::IsExpanded(int32 fullListIndex) 498 { 499 BListItem* item = FullListItemAt(fullListIndex); 500 if (!item) 501 return false; 502 503 return item->IsExpanded(); 504 } 505 506 507 BHandler* 508 BOutlineListView::ResolveSpecifier(BMessage* message, int32 index, 509 BMessage* specifier, int32 what, const char* property) 510 { 511 return BListView::ResolveSpecifier(message, index, specifier, what, 512 property); 513 } 514 515 516 status_t 517 BOutlineListView::GetSupportedSuites(BMessage* data) 518 { 519 return BListView::GetSupportedSuites(data); 520 } 521 522 523 status_t 524 BOutlineListView::Perform(perform_code code, void* _data) 525 { 526 switch (code) { 527 case PERFORM_CODE_MIN_SIZE: 528 ((perform_data_min_size*)_data)->return_value 529 = BOutlineListView::MinSize(); 530 return B_OK; 531 case PERFORM_CODE_MAX_SIZE: 532 ((perform_data_max_size*)_data)->return_value 533 = BOutlineListView::MaxSize(); 534 return B_OK; 535 case PERFORM_CODE_PREFERRED_SIZE: 536 ((perform_data_preferred_size*)_data)->return_value 537 = BOutlineListView::PreferredSize(); 538 return B_OK; 539 case PERFORM_CODE_LAYOUT_ALIGNMENT: 540 ((perform_data_layout_alignment*)_data)->return_value 541 = BOutlineListView::LayoutAlignment(); 542 return B_OK; 543 case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH: 544 ((perform_data_has_height_for_width*)_data)->return_value 545 = BOutlineListView::HasHeightForWidth(); 546 return B_OK; 547 case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH: 548 { 549 perform_data_get_height_for_width* data 550 = (perform_data_get_height_for_width*)_data; 551 BOutlineListView::GetHeightForWidth(data->width, &data->min, 552 &data->max, &data->preferred); 553 return B_OK; 554 } 555 case PERFORM_CODE_SET_LAYOUT: 556 { 557 perform_data_set_layout* data = (perform_data_set_layout*)_data; 558 BOutlineListView::SetLayout(data->layout); 559 return B_OK; 560 } 561 case PERFORM_CODE_LAYOUT_INVALIDATED: 562 { 563 perform_data_layout_invalidated* data 564 = (perform_data_layout_invalidated*)_data; 565 BOutlineListView::LayoutInvalidated(data->descendants); 566 return B_OK; 567 } 568 case PERFORM_CODE_DO_LAYOUT: 569 { 570 BOutlineListView::DoLayout(); 571 return B_OK; 572 } 573 } 574 575 return BListView::Perform(code, _data); 576 } 577 578 579 void 580 BOutlineListView::ResizeToPreferred() 581 { 582 BListView::ResizeToPreferred(); 583 } 584 585 586 void 587 BOutlineListView::GetPreferredSize(float* _width, float* _height) 588 { 589 BListView::GetPreferredSize(_width, _height); 590 } 591 592 593 void 594 BOutlineListView::MakeFocus(bool state) 595 { 596 BListView::MakeFocus(state); 597 } 598 599 600 void 601 BOutlineListView::AllAttached() 602 { 603 BListView::AllAttached(); 604 } 605 606 607 void 608 BOutlineListView::AllDetached() 609 { 610 BListView::AllDetached(); 611 } 612 613 614 void 615 BOutlineListView::DetachedFromWindow() 616 { 617 BListView::DetachedFromWindow(); 618 } 619 620 621 void 622 BOutlineListView::FullListSortItems(int (*compareFunc)(const BListItem* a, 623 const BListItem* b)) 624 { 625 SortItemsUnder(NULL, false, compareFunc); 626 } 627 628 629 void 630 BOutlineListView::SortItemsUnder(BListItem* superItem, bool oneLevelOnly, 631 int (*compareFunc)(const BListItem* a, const BListItem* b)) 632 { 633 // This method is quite complicated: basically, it creates a real tree 634 // from the items of the full list, sorts them as needed, and then 635 // populates the entries back into the full and display lists 636 637 int32 firstIndex = FullListIndexOf(superItem) + 1; 638 int32 lastIndex = firstIndex; 639 BList* tree = _BuildTree(superItem, lastIndex); 640 641 _SortTree(tree, oneLevelOnly, compareFunc); 642 643 // Populate to the full list 644 _PopulateTree(tree, fFullList, firstIndex, false); 645 646 if (superItem == NULL 647 || (superItem->IsItemVisible() && superItem->IsExpanded())) { 648 // Populate to BListView's list 649 firstIndex = fList.IndexOf(superItem) + 1; 650 lastIndex = firstIndex; 651 _PopulateTree(tree, fList, lastIndex, true); 652 653 if (fFirstSelected != -1) { 654 // update selection hints 655 fFirstSelected = _CalcFirstSelected(0); 656 fLastSelected = _CalcLastSelected(CountItems()); 657 } 658 659 // only invalidate what may have changed 660 _RecalcItemTops(firstIndex); 661 BRect top = ItemFrame(firstIndex); 662 BRect bottom = ItemFrame(lastIndex - 1); 663 BRect update(top.left, top.top, bottom.right, bottom.bottom); 664 Invalidate(update); 665 } 666 667 _DestructTree(tree); 668 } 669 670 671 int32 672 BOutlineListView::CountItemsUnder(BListItem* superItem, bool oneLevelOnly) const 673 { 674 int32 i = FullListIndexOf(superItem); 675 if (i == -1) 676 return 0; 677 678 ++i; 679 int32 count = 0; 680 uint32 baseLevel = superItem->OutlineLevel(); 681 682 for (; i < FullListCountItems(); i++) { 683 BListItem* item = FullListItemAt(i); 684 685 // If we jump out of the subtree, return count 686 if (item->fLevel <= baseLevel) 687 return count; 688 689 // If the level matches, increase count 690 if (!oneLevelOnly || item->fLevel == baseLevel + 1) 691 count++; 692 } 693 694 return count; 695 } 696 697 698 BListItem* 699 BOutlineListView::EachItemUnder(BListItem* superItem, bool oneLevelOnly, 700 BListItem* (*eachFunc)(BListItem* item, void* arg), void* arg) 701 { 702 int32 i = IndexOf(superItem); 703 if (i == -1) 704 return NULL; 705 706 while (i < FullListCountItems()) { 707 BListItem* item = FullListItemAt(i); 708 709 // If we jump out of the subtree, return NULL 710 if (item->fLevel < superItem->OutlineLevel()) 711 return NULL; 712 713 // If the level matches, check the index 714 if (!oneLevelOnly || item->fLevel == superItem->OutlineLevel() + 1) { 715 item = eachFunc(item, arg); 716 if (item != NULL) 717 return item; 718 } 719 720 i++; 721 } 722 723 return NULL; 724 } 725 726 727 BListItem* 728 BOutlineListView::ItemUnderAt(BListItem* superItem, bool oneLevelOnly, 729 int32 index) const 730 { 731 int32 i = FullListIndexOf(superItem); 732 if (i == -1) 733 return NULL; 734 735 while (i < FullListCountItems()) { 736 BListItem* item = FullListItemAt(i); 737 738 // If we jump out of the subtree, return NULL 739 if (item->fLevel < superItem->OutlineLevel()) 740 return NULL; 741 742 // If the level matches, check the index 743 if (!oneLevelOnly || item->fLevel == superItem->OutlineLevel() + 1) { 744 if (index == 0) 745 return item; 746 747 index--; 748 } 749 750 i++; 751 } 752 753 return NULL; 754 } 755 756 757 bool 758 BOutlineListView::DoMiscellaneous(MiscCode code, MiscData* data) 759 { 760 if (code == B_SWAP_OP) 761 return _SwapItems(data->swap.a, data->swap.b); 762 763 return BListView::DoMiscellaneous(code, data); 764 } 765 766 767 void 768 BOutlineListView::MessageReceived(BMessage* msg) 769 { 770 BListView::MessageReceived(msg); 771 } 772 773 774 void BOutlineListView::_ReservedOutlineListView1() {} 775 void BOutlineListView::_ReservedOutlineListView2() {} 776 void BOutlineListView::_ReservedOutlineListView3() {} 777 void BOutlineListView::_ReservedOutlineListView4() {} 778 779 780 void 781 BOutlineListView::ExpandOrCollapse(BListItem* item, bool expand) 782 { 783 if (item->IsExpanded() == expand || !FullListHasItem(item)) 784 return; 785 786 item->fExpanded = expand; 787 788 // TODO: merge these cases together, they are pretty similar 789 790 if (expand) { 791 uint32 level = item->fLevel; 792 int32 fullListIndex = FullListIndexOf(item); 793 int32 index = IndexOf(item) + 1; 794 int32 startIndex = index; 795 int32 count = FullListCountItems() - fullListIndex - 1; 796 BListItem** items = (BListItem**)fFullList.Items() + fullListIndex + 1; 797 798 BFont font; 799 GetFont(&font); 800 while (count-- > 0) { 801 item = items[0]; 802 if (item->fLevel <= level) 803 break; 804 805 if (!item->IsItemVisible()) { 806 // fix selection hints 807 if (index <= fFirstSelected) 808 fFirstSelected++; 809 if (index <= fLastSelected) 810 fLastSelected++; 811 812 fList.AddItem(item, index++); 813 item->Update(this, &font); 814 item->SetItemVisible(true); 815 } 816 817 if (item->HasSubitems() && !item->IsExpanded()) { 818 // Skip hidden children 819 uint32 subLevel = item->fLevel; 820 items++; 821 822 while (--count > 0 && items[0]->fLevel > subLevel) 823 items++; 824 } else 825 items++; 826 } 827 _RecalcItemTops(startIndex); 828 } else { 829 // collapse 830 uint32 level = item->fLevel; 831 int32 fullListIndex = FullListIndexOf(item); 832 int32 index = IndexOf(item); 833 int32 startIndex = index; 834 int32 max = FullListCountItems() - fullListIndex - 1; 835 int32 count = 0; 836 bool selectionChanged = false; 837 838 BListItem** items = (BListItem**)fFullList.Items() + fullListIndex + 1; 839 840 while (max-- > 0) { 841 item = items[0]; 842 if (item->fLevel <= level) 843 break; 844 845 if (item->IsItemVisible()) { 846 fList.RemoveItem(item); 847 item->SetItemVisible(false); 848 if (item->IsSelected()) { 849 selectionChanged = true; 850 item->Deselect(); 851 } 852 count++; 853 } 854 855 items++; 856 } 857 858 _RecalcItemTops(startIndex); 859 // fix selection hints 860 // if the selected item was just removed by collapsing, select its 861 // parent 862 if (ListType() == B_SINGLE_SELECTION_LIST && selectionChanged) 863 fFirstSelected = fLastSelected = index; 864 865 if (index < fFirstSelected && index + count < fFirstSelected) { 866 // all items removed were higher than the selection range, 867 // adjust the indexes to correspond to their new visible positions 868 fFirstSelected -= count; 869 fLastSelected -= count; 870 } 871 872 int32 maxIndex = fList.CountItems() - 1; 873 if (fFirstSelected > maxIndex) 874 fFirstSelected = maxIndex; 875 876 if (fLastSelected > maxIndex) 877 fLastSelected = maxIndex; 878 879 if (selectionChanged) 880 SelectionChanged(); 881 } 882 883 _FixupScrollBar(); 884 Invalidate(); 885 } 886 887 888 BRect 889 BOutlineListView::LatchRect(BRect itemRect, int32 level) const 890 { 891 float latchWidth = be_plain_font->Size(); 892 float latchHeight = be_plain_font->Size(); 893 float indentOffset = level * be_control_look->DefaultItemSpacing(); 894 float heightOffset = itemRect.Height() / 2 - latchHeight / 2; 895 896 return BRect(0, 0, latchWidth, latchHeight) 897 .OffsetBySelf(itemRect.left, itemRect.top) 898 .OffsetBySelf(indentOffset, heightOffset); 899 } 900 901 902 void 903 BOutlineListView::DrawLatch(BRect itemRect, int32 level, bool collapsed, 904 bool highlighted, bool misTracked) 905 { 906 BRect latchRect(LatchRect(itemRect, level)); 907 rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR); 908 int32 arrowDirection = collapsed ? BControlLook::B_RIGHT_ARROW 909 : BControlLook::B_DOWN_ARROW; 910 911 be_control_look->DrawArrowShape(this, latchRect, itemRect, base, 912 arrowDirection, 0, B_DARKEN_4_TINT); 913 } 914 915 916 void 917 BOutlineListView::DrawItem(BListItem* item, BRect itemRect, bool complete) 918 { 919 if (item->fHasSubitems) { 920 DrawLatch(itemRect, item->fLevel, !item->IsExpanded(), 921 item->IsSelected() || complete, false); 922 } 923 924 itemRect.left += LatchRect(itemRect, item->fLevel).right; 925 item->DrawItem(this, itemRect, complete); 926 } 927 928 929 int32 930 BOutlineListView::_FullListIndex(int32 index) const 931 { 932 BListItem* item = ItemAt(index); 933 934 if (item == NULL) 935 return -1; 936 937 return FullListIndexOf(item); 938 } 939 940 941 void 942 BOutlineListView::_PopulateTree(BList* tree, BList& target, 943 int32& firstIndex, bool onlyVisible) 944 { 945 BListItem** items = (BListItem**)target.Items(); 946 int32 count = tree->CountItems(); 947 948 for (int32 index = 0; index < count; index++) { 949 BListItem* item = (BListItem*)tree->ItemAtFast(index); 950 951 items[firstIndex++] = item; 952 953 if (item->HasSubitems() && (!onlyVisible || item->IsExpanded())) { 954 _PopulateTree(item->fTemporaryList, target, firstIndex, 955 onlyVisible); 956 } 957 } 958 } 959 960 961 void 962 BOutlineListView::_SortTree(BList* tree, bool oneLevelOnly, 963 int (*compareFunc)(const BListItem* a, const BListItem* b)) 964 { 965 BListItem** items = (BListItem**)tree->Items(); 966 std::sort(items, items + tree->CountItems(), 967 ListItemComparator(compareFunc)); 968 969 if (oneLevelOnly) 970 return; 971 972 for (int32 index = tree->CountItems(); index-- > 0;) { 973 BListItem* item = (BListItem*)tree->ItemAt(index); 974 975 if (item->HasSubitems()) 976 _SortTree(item->fTemporaryList, false, compareFunc); 977 } 978 } 979 980 981 void 982 BOutlineListView::_DestructTree(BList* tree) 983 { 984 for (int32 index = tree->CountItems(); index-- > 0;) { 985 BListItem* item = (BListItem*)tree->ItemAt(index); 986 987 if (item->HasSubitems()) 988 _DestructTree(item->fTemporaryList); 989 } 990 991 delete tree; 992 } 993 994 995 BList* 996 BOutlineListView::_BuildTree(BListItem* superItem, int32& fullListIndex) 997 { 998 int32 fullCount = FullListCountItems(); 999 uint32 level = superItem != NULL ? superItem->OutlineLevel() + 1 : 0; 1000 BList* list = new BList; 1001 if (superItem != NULL) 1002 superItem->fTemporaryList = list; 1003 1004 while (fullListIndex < fullCount) { 1005 BListItem* item = FullListItemAt(fullListIndex); 1006 1007 // If we jump out of the subtree, break out 1008 if (item->fLevel < level) 1009 break; 1010 1011 // If the level matches, put them into the list 1012 // (we handle the case of a missing sublevel gracefully) 1013 list->AddItem(item); 1014 fullListIndex++; 1015 1016 if (item->HasSubitems()) { 1017 // we're going deeper 1018 _BuildTree(item, fullListIndex); 1019 } 1020 } 1021 1022 return list; 1023 } 1024 1025 1026 void 1027 BOutlineListView::_CullInvisibleItems(BList& list) 1028 { 1029 int32 index = 0; 1030 while (index < list.CountItems()) { 1031 if (reinterpret_cast<BListItem*>(list.ItemAt(index))->IsItemVisible()) 1032 ++index; 1033 else 1034 list.RemoveItem(index); 1035 } 1036 } 1037 1038 1039 bool 1040 BOutlineListView::_SwapItems(int32 first, int32 second) 1041 { 1042 // same item, do nothing 1043 if (first == second) 1044 return true; 1045 1046 // fail, first item out of bounds 1047 if ((first < 0) || (first >= CountItems())) 1048 return false; 1049 1050 // fail, second item out of bounds 1051 if ((second < 0) || (second >= CountItems())) 1052 return false; 1053 1054 int32 firstIndex = min_c(first, second); 1055 int32 secondIndex = max_c(first, second); 1056 BListItem* firstItem = ItemAt(firstIndex); 1057 BListItem* secondItem = ItemAt(secondIndex); 1058 BList firstSubItems, secondSubItems; 1059 1060 if (Superitem(firstItem) != Superitem(secondItem)) 1061 return false; 1062 if (!firstItem->IsItemVisible() || !secondItem->IsItemVisible()) 1063 return false; 1064 1065 int32 fullFirstIndex = _FullListIndex(firstIndex); 1066 int32 fullSecondIndex = _FullListIndex(secondIndex); 1067 _GetSubItems(fFullList, firstSubItems, firstItem, fullFirstIndex + 1); 1068 _GetSubItems(fFullList, secondSubItems, secondItem, fullSecondIndex + 1); 1069 _DoSwap(fFullList, fullFirstIndex, fullSecondIndex, &firstSubItems, 1070 &secondSubItems); 1071 1072 _CullInvisibleItems(firstSubItems); 1073 _CullInvisibleItems(secondSubItems); 1074 _DoSwap(fList, firstIndex, secondIndex, &firstSubItems, 1075 &secondSubItems); 1076 1077 _RecalcItemTops(firstIndex); 1078 _RescanSelection(firstIndex, secondIndex + secondSubItems.CountItems()); 1079 Invalidate(Bounds()); 1080 return true; 1081 } 1082 1083 1084 /*! \brief Removes a single item from the list and all of its children. 1085 1086 Unlike the BeOS version, this one will actually delete the children, too, 1087 as there should be no reference left to them. This may cause problems for 1088 applications that actually take the misbehaviour of the Be classes into 1089 account. 1090 */ 1091 BListItem* 1092 BOutlineListView::_RemoveItem(BListItem* item, int32 fullListIndex) 1093 { 1094 if (item == NULL || fullListIndex < 0 1095 || fullListIndex >= FullListCountItems()) { 1096 return NULL; 1097 } 1098 1099 uint32 level = item->OutlineLevel(); 1100 int32 superIndex; 1101 BListItem* super = _SuperitemForIndex(fullListIndex, level, &superIndex); 1102 1103 if (item->IsItemVisible()) { 1104 // remove children, too 1105 while (fullListIndex + 1 < FullListCountItems()) { 1106 BListItem* subItem = FullListItemAt(fullListIndex + 1); 1107 1108 if (subItem->OutlineLevel() <= level) 1109 break; 1110 1111 if (subItem->IsItemVisible()) 1112 BListView::RemoveItem(subItem); 1113 1114 fFullList.RemoveItem(fullListIndex + 1); 1115 delete subItem; 1116 } 1117 BListView::RemoveItem(item); 1118 } 1119 1120 fFullList.RemoveItem(fullListIndex); 1121 1122 if (super != NULL) { 1123 // we might need to change the fHasSubitems field of the parent 1124 BListItem* child = FullListItemAt(superIndex + 1); 1125 if (child == NULL || child->OutlineLevel() <= super->OutlineLevel()) 1126 super->fHasSubitems = false; 1127 } 1128 return item; 1129 } 1130 1131 1132 /*! Returns the super item before the item specified by \a fullListIndex 1133 and \a level. 1134 */ 1135 BListItem* 1136 BOutlineListView::_SuperitemForIndex(int32 fullListIndex, int32 level, 1137 int32* _superIndex) 1138 { 1139 BListItem* item; 1140 fullListIndex--; 1141 1142 while (fullListIndex >= 0) { 1143 if ((item = FullListItemAt(fullListIndex))->OutlineLevel() 1144 < (uint32)level) { 1145 if (_superIndex != NULL) 1146 *_superIndex = fullListIndex; 1147 return item; 1148 } 1149 1150 fullListIndex--; 1151 } 1152 1153 return NULL; 1154 } 1155 1156 1157 int32 1158 BOutlineListView::_FindPreviousVisibleIndex(int32 fullListIndex) 1159 { 1160 fullListIndex--; 1161 1162 while (fullListIndex >= 0) { 1163 if (FullListItemAt(fullListIndex)->fVisible) 1164 return fullListIndex; 1165 1166 fullListIndex--; 1167 } 1168 1169 return -1; 1170 } 1171