xref: /haiku/src/kits/interface/OutlineListView.cpp (revision 445d4fd926c569e7b9ae28017da86280aaecbae2)
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 {
34 	ListItemComparator(compare_func compareFunc)
35 		:
36 		fCompareFunc(compareFunc)
37 	{
38 	}
39 
40 	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
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
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 
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 
90 BOutlineListView::BOutlineListView(const char* name, list_view_type type,
91 	uint32 flags)
92 	:
93 	BListView(name, type, flags)
94 {
95 }
96 
97 
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 
116 BOutlineListView::~BOutlineListView()
117 {
118 	fFullList.MakeEmpty();
119 }
120 
121 
122 BArchivable*
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
133 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
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
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
234 BOutlineListView::FrameMoved(BPoint newPosition)
235 {
236 	BListView::FrameMoved(newPosition);
237 }
238 
239 
240 void
241 BOutlineListView::FrameResized(float newWidth, float newHeight)
242 {
243 	BListView::FrameResized(newWidth, newHeight);
244 }
245 
246 
247 void
248 BOutlineListView::MouseUp(BPoint where)
249 {
250 	BListView::MouseUp(where);
251 }
252 
253 
254 bool
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
280 BOutlineListView::AddItem(BListItem* item)
281 {
282 	return AddItem(item, FullListCountItems());
283 }
284 
285 
286 bool
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
332 BOutlineListView::AddList(BList* newItems)
333 {
334 	return AddList(newItems, FullListCountItems());
335 }
336 
337 
338 bool
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
352 BOutlineListView::RemoveItem(BListItem* item)
353 {
354 	return _RemoveItem(item, FullListIndexOf(item)) != NULL;
355 }
356 
357 
358 BListItem*
359 BOutlineListView::RemoveItem(int32 fullListIndex)
360 {
361 	return _RemoveItem(FullListItemAt(fullListIndex), fullListIndex);
362 }
363 
364 
365 bool
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*
382 BOutlineListView::FullListItemAt(int32 fullListIndex) const
383 {
384 	return (BListItem*)fFullList.ItemAt(fullListIndex);
385 }
386 
387 
388 int32
389 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
401 BOutlineListView::FullListIndexOf(BListItem* item) const
402 {
403 	return fFullList.IndexOf(item);
404 }
405 
406 
407 BListItem*
408 BOutlineListView::FullListFirstItem() const
409 {
410 	return (BListItem*)fFullList.FirstItem();
411 }
412 
413 
414 BListItem*
415 BOutlineListView::FullListLastItem() const
416 {
417 	return (BListItem*)fFullList.LastItem();
418 }
419 
420 
421 bool
422 BOutlineListView::FullListHasItem(BListItem* item) const
423 {
424 	return fFullList.HasItem(item);
425 }
426 
427 
428 int32
429 BOutlineListView::FullListCountItems() const
430 {
431 	return fFullList.CountItems();
432 }
433 
434 
435 int32
436 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
449 BOutlineListView::MakeEmpty()
450 {
451 	fFullList.MakeEmpty();
452 	BListView::MakeEmpty();
453 }
454 
455 
456 bool
457 BOutlineListView::FullListIsEmpty() const
458 {
459 	return fFullList.IsEmpty();
460 }
461 
462 
463 void
464 BOutlineListView::FullListDoForEach(bool(*func)(BListItem* item))
465 {
466 	fFullList.DoForEach(reinterpret_cast<bool (*)(void*)>(func));
467 }
468 
469 
470 void
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*
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
490 BOutlineListView::Expand(BListItem* item)
491 {
492 	ExpandOrCollapse(item, true);
493 }
494 
495 
496 void
497 BOutlineListView::Collapse(BListItem* item)
498 {
499 	ExpandOrCollapse(item, false);
500 }
501 
502 
503 bool
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*
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
524 BOutlineListView::GetSupportedSuites(BMessage* data)
525 {
526 	return BListView::GetSupportedSuites(data);
527 }
528 
529 
530 status_t
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
587 BOutlineListView::ResizeToPreferred()
588 {
589 	BListView::ResizeToPreferred();
590 }
591 
592 
593 void
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
620 BOutlineListView::MakeFocus(bool state)
621 {
622 	BListView::MakeFocus(state);
623 }
624 
625 
626 void
627 BOutlineListView::AllAttached()
628 {
629 	BListView::AllAttached();
630 }
631 
632 
633 void
634 BOutlineListView::AllDetached()
635 {
636 	BListView::AllDetached();
637 }
638 
639 
640 void
641 BOutlineListView::DetachedFromWindow()
642 {
643 	BListView::DetachedFromWindow();
644 }
645 
646 
647 void
648 BOutlineListView::FullListSortItems(int (*compareFunc)(const BListItem* a,
649 	const BListItem* b))
650 {
651 	SortItemsUnder(NULL, false, compareFunc);
652 }
653 
654 
655 void
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
698 BOutlineListView::CountItemsUnder(BListItem* superItem, bool oneLevelOnly) const
699 {
700 	int32 i = FullListIndexOf(superItem);
701 	if (i == -1)
702 		return 0;
703 
704 	++i;
705 	int32 count = 0;
706 	uint32 baseLevel = superItem->OutlineLevel();
707 
708 	for (; i < FullListCountItems(); i++) {
709 		BListItem* item = FullListItemAt(i);
710 
711 		// If we jump out of the subtree, return count
712 		if (item->fLevel <= baseLevel)
713 			return count;
714 
715 		// If the level matches, increase count
716 		if (!oneLevelOnly || item->fLevel == baseLevel + 1)
717 			count++;
718 	}
719 
720 	return count;
721 }
722 
723 
724 BListItem*
725 BOutlineListView::EachItemUnder(BListItem* superItem, bool oneLevelOnly,
726 	BListItem* (*eachFunc)(BListItem* item, void* arg), void* arg)
727 {
728 	int32 i = FullListIndexOf(superItem);
729 	if (i == -1)
730 		return NULL;
731 
732 	i++; // skip the superitem
733 	while (i < FullListCountItems()) {
734 		BListItem* item = FullListItemAt(i);
735 
736 		// If we jump out of the subtree, return NULL
737 		if (item->fLevel <= superItem->OutlineLevel())
738 			return NULL;
739 
740 		// If the level matches, check the index
741 		if (!oneLevelOnly || item->fLevel == superItem->OutlineLevel() + 1) {
742 			item = eachFunc(item, arg);
743 			if (item != NULL)
744 				return item;
745 		}
746 
747 		i++;
748 	}
749 
750 	return NULL;
751 }
752 
753 
754 BListItem*
755 BOutlineListView::ItemUnderAt(BListItem* superItem, bool oneLevelOnly,
756 	int32 index) const
757 {
758 	int32 i = FullListIndexOf(superItem);
759 	if (i == -1)
760 		return NULL;
761 
762 	while (i < FullListCountItems()) {
763 		BListItem* item = FullListItemAt(i);
764 
765 		// If we jump out of the subtree, return NULL
766 		if (item->fLevel < superItem->OutlineLevel())
767 			return NULL;
768 
769 		// If the level matches, check the index
770 		if (!oneLevelOnly || item->fLevel == superItem->OutlineLevel() + 1) {
771 			if (index == 0)
772 				return item;
773 
774 			index--;
775 		}
776 
777 		i++;
778 	}
779 
780 	return NULL;
781 }
782 
783 
784 bool
785 BOutlineListView::DoMiscellaneous(MiscCode code, MiscData* data)
786 {
787 	if (code == B_SWAP_OP)
788 		return _SwapItems(data->swap.a, data->swap.b);
789 
790 	return BListView::DoMiscellaneous(code, data);
791 }
792 
793 
794 void
795 BOutlineListView::MessageReceived(BMessage* msg)
796 {
797 	BListView::MessageReceived(msg);
798 }
799 
800 
801 void BOutlineListView::_ReservedOutlineListView1() {}
802 void BOutlineListView::_ReservedOutlineListView2() {}
803 void BOutlineListView::_ReservedOutlineListView3() {}
804 void BOutlineListView::_ReservedOutlineListView4() {}
805 
806 
807 void
808 BOutlineListView::ExpandOrCollapse(BListItem* item, bool expand)
809 {
810 	if (item->IsExpanded() == expand || !FullListHasItem(item))
811 		return;
812 
813 	item->fExpanded = expand;
814 
815 	// TODO: merge these cases together, they are pretty similar
816 
817 	if (expand) {
818 		uint32 level = item->fLevel;
819 		int32 fullListIndex = FullListIndexOf(item);
820 		int32 index = IndexOf(item) + 1;
821 		int32 startIndex = index;
822 		int32 count = FullListCountItems() - fullListIndex - 1;
823 		BListItem** items = (BListItem**)fFullList.Items() + fullListIndex + 1;
824 
825 		BFont font;
826 		GetFont(&font);
827 		while (count-- > 0) {
828 			item = items[0];
829 			if (item->fLevel <= level)
830 				break;
831 
832 			if (!item->IsItemVisible()) {
833 				// fix selection hints
834 				if (index <= fFirstSelected)
835 					fFirstSelected++;
836 				if (index <= fLastSelected)
837 					fLastSelected++;
838 
839 				fList.AddItem(item, index++);
840 				item->Update(this, &font);
841 				item->SetItemVisible(true);
842 			}
843 
844 			if (item->HasSubitems() && !item->IsExpanded()) {
845 				// Skip hidden children
846 				uint32 subLevel = item->fLevel;
847 				items++;
848 
849 				while (count > 0 && items[0]->fLevel > subLevel) {
850 					items++;
851 					count--;
852 				}
853 			} else
854 				items++;
855 		}
856 		_RecalcItemTops(startIndex);
857 	} else {
858 		// collapse
859 		const uint32 level = item->fLevel;
860 		const int32 fullListIndex = FullListIndexOf(item);
861 		const int32 index = IndexOf(item);
862 		int32 max = FullListCountItems() - fullListIndex - 1;
863 		int32 count = 0;
864 		bool selectionChanged = false;
865 
866 		BListItem** items = (BListItem**)fFullList.Items() + fullListIndex + 1;
867 
868 		while (max-- > 0) {
869 			item = items[0];
870 			if (item->fLevel <= level)
871 				break;
872 
873 			if (item->IsItemVisible()) {
874 				fList.RemoveItem(item);
875 				item->SetItemVisible(false);
876 				if (item->IsSelected()) {
877 					selectionChanged = true;
878 					item->Deselect();
879 				}
880 				count++;
881 			}
882 
883 			items++;
884 		}
885 
886 		_RecalcItemTops(index);
887 		// fix selection hints
888 		// if the selected item was just removed by collapsing, select its
889 		// parent
890 		if (selectionChanged) {
891 			if (fFirstSelected > index && fFirstSelected <= index + count) {
892 					fFirstSelected = index;
893 			}
894 			if (fLastSelected > index && fLastSelected <= index + count) {
895 				fLastSelected = index;
896 			}
897 		}
898 		if (index + count < fFirstSelected) {
899 				// all items removed were higher than the selection range,
900 				// adjust the indexes to correspond to their new visible positions
901 				fFirstSelected -= count;
902 				fLastSelected -= count;
903 		}
904 
905 		int32 maxIndex = fList.CountItems() - 1;
906 		if (fFirstSelected > maxIndex)
907 			fFirstSelected = maxIndex;
908 
909 		if (fLastSelected > maxIndex)
910 			fLastSelected = maxIndex;
911 
912 		if (selectionChanged)
913 			Select(fFirstSelected, fLastSelected);
914 	}
915 
916 	_FixupScrollBar();
917 	Invalidate();
918 }
919 
920 
921 BRect
922 BOutlineListView::LatchRect(BRect itemRect, int32 level) const
923 {
924 	float latchWidth = be_plain_font->Size();
925 	float latchHeight = be_plain_font->Size();
926 	float indentOffset = level * be_control_look->DefaultItemSpacing();
927 	float heightOffset = itemRect.Height() / 2 - latchHeight / 2;
928 
929 	return BRect(0, 0, latchWidth, latchHeight)
930 		.OffsetBySelf(itemRect.left, itemRect.top)
931 		.OffsetBySelf(indentOffset, heightOffset);
932 }
933 
934 
935 void
936 BOutlineListView::DrawLatch(BRect itemRect, int32 level, bool collapsed,
937 	bool highlighted, bool misTracked)
938 {
939 	BRect latchRect(LatchRect(itemRect, level));
940 	rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR);
941 	int32 arrowDirection = collapsed ? BControlLook::B_RIGHT_ARROW
942 		: BControlLook::B_DOWN_ARROW;
943 
944 	float tintColor = B_DARKEN_4_TINT;
945 	if (base.red + base.green + base.blue <= 128 * 3) {
946 		tintColor = B_LIGHTEN_2_TINT;
947 	}
948 
949 	be_control_look->DrawArrowShape(this, latchRect, itemRect, base,
950 		arrowDirection, 0, tintColor);
951 }
952 
953 
954 void
955 BOutlineListView::DrawItem(BListItem* item, BRect itemRect, bool complete)
956 {
957 	if (item->fHasSubitems) {
958 		DrawLatch(itemRect, item->fLevel, !item->IsExpanded(),
959 			item->IsSelected() || complete, false);
960 	}
961 
962 	itemRect.left += LatchRect(itemRect, item->fLevel).right;
963 	BListView::DrawItem(item, itemRect, complete);
964 }
965 
966 
967 int32
968 BOutlineListView::_FullListIndex(int32 index) const
969 {
970 	BListItem* item = ItemAt(index);
971 
972 	if (item == NULL)
973 		return -1;
974 
975 	return FullListIndexOf(item);
976 }
977 
978 
979 void
980 BOutlineListView::_PopulateTree(BList* tree, BList& target,
981 	int32& firstIndex, bool onlyVisible)
982 {
983 	BListItem** items = (BListItem**)target.Items();
984 	int32 count = tree->CountItems();
985 
986 	for (int32 index = 0; index < count; index++) {
987 		BListItem* item = (BListItem*)tree->ItemAtFast(index);
988 
989 		items[firstIndex++] = item;
990 
991 		if (item->HasSubitems() && (!onlyVisible || item->IsExpanded())) {
992 			_PopulateTree(item->fTemporaryList, target, firstIndex,
993 				onlyVisible);
994 		}
995 	}
996 }
997 
998 
999 void
1000 BOutlineListView::_SortTree(BList* tree, bool oneLevelOnly,
1001 	int (*compareFunc)(const BListItem* a, const BListItem* b))
1002 {
1003 	BListItem** items = (BListItem**)tree->Items();
1004 	std::sort(items, items + tree->CountItems(),
1005 		ListItemComparator(compareFunc));
1006 
1007 	if (oneLevelOnly)
1008 		return;
1009 
1010 	for (int32 index = tree->CountItems(); index-- > 0;) {
1011 		BListItem* item = (BListItem*)tree->ItemAt(index);
1012 
1013 		if (item->HasSubitems())
1014 			_SortTree(item->fTemporaryList, false, compareFunc);
1015 	}
1016 }
1017 
1018 
1019 void
1020 BOutlineListView::_DestructTree(BList* tree)
1021 {
1022 	for (int32 index = tree->CountItems(); index-- > 0;) {
1023 		BListItem* item = (BListItem*)tree->ItemAt(index);
1024 
1025 		if (item->HasSubitems())
1026 			_DestructTree(item->fTemporaryList);
1027 	}
1028 
1029 	delete tree;
1030 }
1031 
1032 
1033 BList*
1034 BOutlineListView::_BuildTree(BListItem* superItem, int32& fullListIndex)
1035 {
1036 	int32 fullCount = FullListCountItems();
1037 	uint32 level = superItem != NULL ? superItem->OutlineLevel() + 1 : 0;
1038 	BList* list = new BList;
1039 	if (superItem != NULL)
1040 		superItem->fTemporaryList = list;
1041 
1042 	while (fullListIndex < fullCount) {
1043 		BListItem* item = FullListItemAt(fullListIndex);
1044 
1045 		// If we jump out of the subtree, break out
1046 		if (item->fLevel < level)
1047 			break;
1048 
1049 		// If the level matches, put them into the list
1050 		// (we handle the case of a missing sublevel gracefully)
1051 		list->AddItem(item);
1052 		fullListIndex++;
1053 
1054 		if (item->HasSubitems()) {
1055 			// we're going deeper
1056 			_BuildTree(item, fullListIndex);
1057 		}
1058 	}
1059 
1060 	return list;
1061 }
1062 
1063 
1064 void
1065 BOutlineListView::_CullInvisibleItems(BList& list)
1066 {
1067 	int32 index = 0;
1068 	while (index < list.CountItems()) {
1069 		if (reinterpret_cast<BListItem*>(list.ItemAt(index))->IsItemVisible())
1070 			++index;
1071 		else
1072 			list.RemoveItem(index);
1073 	}
1074 }
1075 
1076 
1077 bool
1078 BOutlineListView::_SwapItems(int32 first, int32 second)
1079 {
1080 	// same item, do nothing
1081 	if (first == second)
1082 		return true;
1083 
1084 	// fail, first item out of bounds
1085 	if ((first < 0) || (first >= CountItems()))
1086 		return false;
1087 
1088 	// fail, second item out of bounds
1089 	if ((second < 0) || (second >= CountItems()))
1090 		return false;
1091 
1092 	int32 firstIndex = min_c(first, second);
1093 	int32 secondIndex = max_c(first, second);
1094 	BListItem* firstItem = ItemAt(firstIndex);
1095 	BListItem* secondItem = ItemAt(secondIndex);
1096 	BList firstSubItems, secondSubItems;
1097 
1098 	if (Superitem(firstItem) != Superitem(secondItem))
1099 		return false;
1100 
1101 	if (!firstItem->IsItemVisible() || !secondItem->IsItemVisible())
1102 		return false;
1103 
1104 	int32 fullFirstIndex = _FullListIndex(firstIndex);
1105 	int32 fullSecondIndex = _FullListIndex(secondIndex);
1106 	_GetSubItems(fFullList, firstSubItems, firstItem, fullFirstIndex + 1);
1107 	_GetSubItems(fFullList, secondSubItems, secondItem, fullSecondIndex + 1);
1108 	_DoSwap(fFullList, fullFirstIndex, fullSecondIndex, &firstSubItems,
1109 		&secondSubItems);
1110 
1111 	_CullInvisibleItems(firstSubItems);
1112 	_CullInvisibleItems(secondSubItems);
1113 	_DoSwap(fList, firstIndex, secondIndex, &firstSubItems,
1114 		&secondSubItems);
1115 
1116 	_RecalcItemTops(firstIndex);
1117 	_RescanSelection(firstIndex, secondIndex + secondSubItems.CountItems());
1118 	Invalidate(Bounds());
1119 
1120 	return true;
1121 }
1122 
1123 
1124 /*!	\brief Removes a single item from the list and all of its children.
1125 
1126 	Unlike the BeOS version, this one will actually delete the children, too,
1127 	as there should be no reference left to them. This may cause problems for
1128 	applications that actually take the misbehaviour of the Be classes into
1129 	account.
1130 */
1131 BListItem*
1132 BOutlineListView::_RemoveItem(BListItem* item, int32 fullListIndex)
1133 {
1134 	if (item == NULL || fullListIndex < 0
1135 		|| fullListIndex >= FullListCountItems()) {
1136 		return NULL;
1137 	}
1138 
1139 	uint32 level = item->OutlineLevel();
1140 	int32 superIndex;
1141 	BListItem* super = _SuperitemForIndex(fullListIndex, level, &superIndex);
1142 
1143 	if (item->IsItemVisible()) {
1144 		// remove children, too
1145 		while (fullListIndex + 1 < FullListCountItems()) {
1146 			BListItem* subItem = FullListItemAt(fullListIndex + 1);
1147 
1148 			if (subItem->OutlineLevel() <= level)
1149 				break;
1150 
1151 			if (subItem->IsItemVisible())
1152 				BListView::RemoveItem(subItem);
1153 
1154 			fFullList.RemoveItem(fullListIndex + 1);
1155 			delete subItem;
1156 		}
1157 		BListView::RemoveItem(item);
1158 	}
1159 
1160 	fFullList.RemoveItem(fullListIndex);
1161 
1162 	if (super != NULL) {
1163 		// we might need to change the fHasSubitems field of the parent
1164 		BListItem* child = FullListItemAt(superIndex + 1);
1165 		if (child == NULL || child->OutlineLevel() <= super->OutlineLevel())
1166 			super->fHasSubitems = false;
1167 	}
1168 
1169 	return item;
1170 }
1171 
1172 
1173 /*!	Returns the super item before the item specified by \a fullListIndex
1174 	and \a level.
1175 */
1176 BListItem*
1177 BOutlineListView::_SuperitemForIndex(int32 fullListIndex, int32 level,
1178 	int32* _superIndex)
1179 {
1180 	BListItem* item;
1181 	fullListIndex--;
1182 
1183 	while (fullListIndex >= 0) {
1184 		if ((item = FullListItemAt(fullListIndex))->OutlineLevel()
1185 				< (uint32)level) {
1186 			if (_superIndex != NULL)
1187 				*_superIndex = fullListIndex;
1188 			return item;
1189 		}
1190 
1191 		fullListIndex--;
1192 	}
1193 
1194 	return NULL;
1195 }
1196 
1197 
1198 int32
1199 BOutlineListView::_FindPreviousVisibleIndex(int32 fullListIndex)
1200 {
1201 	fullListIndex--;
1202 
1203 	while (fullListIndex >= 0) {
1204 		if (FullListItemAt(fullListIndex)->fVisible)
1205 			return fullListIndex;
1206 
1207 		fullListIndex--;
1208 	}
1209 
1210 	return -1;
1211 }
1212