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