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