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