xref: /haiku/src/kits/interface/OutlineListView.cpp (revision 3b07762c548ec4016dea480d1061577cd15ec614)
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* msg, int32 index,
509 	BMessage* specifier, int32 what, const char* property)
510 {
511 	return BListView::ResolveSpecifier(msg, index, specifier, what, property);
512 }
513 
514 
515 status_t
516 BOutlineListView::GetSupportedSuites(BMessage* data)
517 {
518 	return BListView::GetSupportedSuites(data);
519 }
520 
521 
522 status_t
523 BOutlineListView::Perform(perform_code code, void* _data)
524 {
525 	switch (code) {
526 		case PERFORM_CODE_MIN_SIZE:
527 			((perform_data_min_size*)_data)->return_value
528 				= BOutlineListView::MinSize();
529 			return B_OK;
530 		case PERFORM_CODE_MAX_SIZE:
531 			((perform_data_max_size*)_data)->return_value
532 				= BOutlineListView::MaxSize();
533 			return B_OK;
534 		case PERFORM_CODE_PREFERRED_SIZE:
535 			((perform_data_preferred_size*)_data)->return_value
536 				= BOutlineListView::PreferredSize();
537 			return B_OK;
538 		case PERFORM_CODE_LAYOUT_ALIGNMENT:
539 			((perform_data_layout_alignment*)_data)->return_value
540 				= BOutlineListView::LayoutAlignment();
541 			return B_OK;
542 		case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH:
543 			((perform_data_has_height_for_width*)_data)->return_value
544 				= BOutlineListView::HasHeightForWidth();
545 			return B_OK;
546 		case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH:
547 		{
548 			perform_data_get_height_for_width* data
549 				= (perform_data_get_height_for_width*)_data;
550 			BOutlineListView::GetHeightForWidth(data->width, &data->min,
551 				&data->max, &data->preferred);
552 			return B_OK;
553 		}
554 		case PERFORM_CODE_SET_LAYOUT:
555 		{
556 			perform_data_set_layout* data = (perform_data_set_layout*)_data;
557 			BOutlineListView::SetLayout(data->layout);
558 			return B_OK;
559 		}
560 		case PERFORM_CODE_LAYOUT_INVALIDATED:
561 		{
562 			perform_data_layout_invalidated* data
563 				= (perform_data_layout_invalidated*)_data;
564 			BOutlineListView::LayoutInvalidated(data->descendants);
565 			return B_OK;
566 		}
567 		case PERFORM_CODE_DO_LAYOUT:
568 		{
569 			BOutlineListView::DoLayout();
570 			return B_OK;
571 		}
572 	}
573 
574 	return BListView::Perform(code, _data);
575 }
576 
577 
578 void
579 BOutlineListView::ResizeToPreferred()
580 {
581 	BListView::ResizeToPreferred();
582 }
583 
584 
585 void
586 BOutlineListView::GetPreferredSize(float* _width, float* _height)
587 {
588 	BListView::GetPreferredSize(_width, _height);
589 }
590 
591 
592 void
593 BOutlineListView::MakeFocus(bool state)
594 {
595 	BListView::MakeFocus(state);
596 }
597 
598 
599 void
600 BOutlineListView::AllAttached()
601 {
602 	BListView::AllAttached();
603 }
604 
605 
606 void
607 BOutlineListView::AllDetached()
608 {
609 	BListView::AllDetached();
610 }
611 
612 
613 void
614 BOutlineListView::DetachedFromWindow()
615 {
616 	BListView::DetachedFromWindow();
617 }
618 
619 
620 void
621 BOutlineListView::FullListSortItems(int (*compareFunc)(const BListItem* a,
622 	const BListItem* b))
623 {
624 	SortItemsUnder(NULL, false, compareFunc);
625 }
626 
627 
628 void
629 BOutlineListView::SortItemsUnder(BListItem* superItem, bool oneLevelOnly,
630 	int (*compareFunc)(const BListItem* a, const BListItem* b))
631 {
632 	// This method is quite complicated: basically, it creates a real tree
633 	// from the items of the full list, sorts them as needed, and then
634 	// populates the entries back into the full and display lists
635 
636 	int32 firstIndex = FullListIndexOf(superItem) + 1;
637 	int32 lastIndex = firstIndex;
638 	BList* tree = _BuildTree(superItem, lastIndex);
639 
640 	_SortTree(tree, oneLevelOnly, compareFunc);
641 
642 	// Populate to the full list
643 	_PopulateTree(tree, fFullList, firstIndex, false);
644 
645 	if (superItem == NULL
646 		|| (superItem->IsItemVisible() && superItem->IsExpanded())) {
647 		// Populate to BListView's list
648 		firstIndex = fList.IndexOf(superItem) + 1;
649 		lastIndex = firstIndex;
650 		_PopulateTree(tree, fList, lastIndex, true);
651 
652 		if (fFirstSelected != -1) {
653 			// update selection hints
654 			fFirstSelected = _CalcFirstSelected(0);
655 			fLastSelected = _CalcLastSelected(CountItems());
656 		}
657 
658 		// only invalidate what may have changed
659 		_RecalcItemTops(firstIndex);
660 		BRect top = ItemFrame(firstIndex);
661 		BRect bottom = ItemFrame(lastIndex - 1);
662 		BRect update(top.left, top.top, bottom.right, bottom.bottom);
663 		Invalidate(update);
664 	}
665 
666 	_DestructTree(tree);
667 }
668 
669 
670 int32
671 BOutlineListView::CountItemsUnder(BListItem* superItem, bool oneLevelOnly) const
672 {
673 	int32 i = FullListIndexOf(superItem);
674 	if (i == -1)
675 		return 0;
676 
677 	++i;
678 	int32 count = 0;
679 	uint32 baseLevel = superItem->OutlineLevel();
680 
681 	for (; i < FullListCountItems(); i++) {
682 		BListItem* item = FullListItemAt(i);
683 
684 		// If we jump out of the subtree, return count
685 		if (item->fLevel <= baseLevel)
686 			return count;
687 
688 		// If the level matches, increase count
689 		if (!oneLevelOnly || item->fLevel == baseLevel + 1)
690 			count++;
691 	}
692 
693 	return count;
694 }
695 
696 
697 BListItem*
698 BOutlineListView::EachItemUnder(BListItem* superItem, bool oneLevelOnly,
699 	BListItem* (*eachFunc)(BListItem* item, void* arg), void* arg)
700 {
701 	int32 i = IndexOf(superItem);
702 	if (i == -1)
703 		return NULL;
704 
705 	while (i < FullListCountItems()) {
706 		BListItem* item = FullListItemAt(i);
707 
708 		// If we jump out of the subtree, return NULL
709 		if (item->fLevel < superItem->OutlineLevel())
710 			return NULL;
711 
712 		// If the level matches, check the index
713 		if (!oneLevelOnly || item->fLevel == superItem->OutlineLevel() + 1) {
714 			item = eachFunc(item, arg);
715 			if (item != NULL)
716 				return item;
717 		}
718 
719 		i++;
720 	}
721 
722 	return NULL;
723 }
724 
725 
726 BListItem*
727 BOutlineListView::ItemUnderAt(BListItem* superItem, bool oneLevelOnly,
728 	int32 index) const
729 {
730 	int32 i = FullListIndexOf(superItem);
731 	if (i == -1)
732 		return NULL;
733 
734 	while (i < FullListCountItems()) {
735 		BListItem* item = FullListItemAt(i);
736 
737 		// If we jump out of the subtree, return NULL
738 		if (item->fLevel < superItem->OutlineLevel())
739 			return NULL;
740 
741 		// If the level matches, check the index
742 		if (!oneLevelOnly || item->fLevel == superItem->OutlineLevel() + 1) {
743 			if (index == 0)
744 				return item;
745 
746 			index--;
747 		}
748 
749 		i++;
750 	}
751 
752 	return NULL;
753 }
754 
755 
756 bool
757 BOutlineListView::DoMiscellaneous(MiscCode code, MiscData* data)
758 {
759 	if (code == B_SWAP_OP)
760 		return _SwapItems(data->swap.a, data->swap.b);
761 
762 	return BListView::DoMiscellaneous(code, data);
763 }
764 
765 
766 void
767 BOutlineListView::MessageReceived(BMessage* msg)
768 {
769 	BListView::MessageReceived(msg);
770 }
771 
772 
773 void BOutlineListView::_ReservedOutlineListView1() {}
774 void BOutlineListView::_ReservedOutlineListView2() {}
775 void BOutlineListView::_ReservedOutlineListView3() {}
776 void BOutlineListView::_ReservedOutlineListView4() {}
777 
778 
779 void
780 BOutlineListView::ExpandOrCollapse(BListItem* item, bool expand)
781 {
782 	if (item->IsExpanded() == expand || !FullListHasItem(item))
783 		return;
784 
785 	item->fExpanded = expand;
786 
787 	// TODO: merge these cases together, they are pretty similar
788 
789 	if (expand) {
790 		uint32 level = item->fLevel;
791 		int32 fullListIndex = FullListIndexOf(item);
792 		int32 index = IndexOf(item) + 1;
793 		int32 startIndex = index;
794 		int32 count = FullListCountItems() - fullListIndex - 1;
795 		BListItem** items = (BListItem**)fFullList.Items() + fullListIndex + 1;
796 
797 		BFont font;
798 		GetFont(&font);
799 		while (count-- > 0) {
800 			item = items[0];
801 			if (item->fLevel <= level)
802 				break;
803 
804 			if (!item->IsItemVisible()) {
805 				// fix selection hints
806 				if (index <= fFirstSelected)
807 					fFirstSelected++;
808 				if (index <= fLastSelected)
809 					fLastSelected++;
810 
811 				fList.AddItem(item, index++);
812 				item->Update(this, &font);
813 				item->SetItemVisible(true);
814 			}
815 
816 			if (item->HasSubitems() && !item->IsExpanded()) {
817 				// Skip hidden children
818 				uint32 subLevel = item->fLevel;
819 				items++;
820 
821 				while (--count > 0 && items[0]->fLevel > subLevel)
822 					items++;
823 			} else
824 				items++;
825 		}
826 		_RecalcItemTops(startIndex);
827 	} else {
828 		// collapse
829 		uint32 level = item->fLevel;
830 		int32 fullListIndex = FullListIndexOf(item);
831 		int32 index = IndexOf(item);
832 		int32 startIndex = index;
833 		int32 max = FullListCountItems() - fullListIndex - 1;
834 		int32 count = 0;
835 		bool selectionChanged = false;
836 
837 		BListItem** items = (BListItem**)fFullList.Items() + fullListIndex + 1;
838 
839 		while (max-- > 0) {
840 			item = items[0];
841 			if (item->fLevel <= level)
842 				break;
843 
844 			if (item->IsItemVisible()) {
845 				fList.RemoveItem(item);
846 				item->SetItemVisible(false);
847 				if (item->IsSelected()) {
848 					selectionChanged = true;
849 					item->Deselect();
850 				}
851 				count++;
852 			}
853 
854 			items++;
855 		}
856 
857 		_RecalcItemTops(startIndex);
858 		// fix selection hints
859 		// if the selected item was just removed by collapsing, select its
860 		// parent
861 		if (ListType() == B_SINGLE_SELECTION_LIST && selectionChanged)
862 			fFirstSelected = fLastSelected = index;
863 
864 		if (index < fFirstSelected && index + count < fFirstSelected) {
865 				// all items removed were higher than the selection range,
866 				// adjust the indexes to correspond to their new visible positions
867 				fFirstSelected -= count;
868 				fLastSelected -= count;
869 		}
870 
871 		int32 maxIndex = fList.CountItems() - 1;
872 		if (fFirstSelected > maxIndex)
873 			fFirstSelected = maxIndex;
874 
875 		if (fLastSelected > maxIndex)
876 			fLastSelected = maxIndex;
877 
878 		if (selectionChanged)
879 			SelectionChanged();
880 	}
881 
882 	_FixupScrollBar();
883 	Invalidate();
884 }
885 
886 
887 BRect
888 BOutlineListView::LatchRect(BRect itemRect, int32 level) const
889 {
890 	float latchWidth = be_plain_font->Size();
891 	float latchHeight = be_plain_font->Size();
892 	float indentOffset = level * be_control_look->DefaultItemSpacing();
893 	float heightOffset = itemRect.Height() / 2 - latchHeight / 2;
894 
895 	return BRect(0, 0, latchWidth, latchHeight)
896 		.OffsetBySelf(itemRect.left, itemRect.top)
897 		.OffsetBySelf(indentOffset, heightOffset);
898 }
899 
900 
901 void
902 BOutlineListView::DrawLatch(BRect itemRect, int32 level, bool collapsed,
903 	bool highlighted, bool misTracked)
904 {
905 	BRect latchRect(LatchRect(itemRect, level));
906 	rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR);
907 	int32 arrowDirection = collapsed ? BControlLook::B_RIGHT_ARROW
908 		: BControlLook::B_DOWN_ARROW;
909 
910 	be_control_look->DrawArrowShape(this, latchRect, itemRect, base,
911 		arrowDirection, 0, B_DARKEN_4_TINT);
912 }
913 
914 
915 void
916 BOutlineListView::DrawItem(BListItem* item, BRect itemRect, bool complete)
917 {
918 	if (item->fHasSubitems) {
919 		DrawLatch(itemRect, item->fLevel, !item->IsExpanded(),
920 			item->IsSelected() || complete, false);
921 	}
922 
923 	itemRect.left += LatchRect(itemRect, item->fLevel).right;
924 	item->DrawItem(this, itemRect, complete);
925 }
926 
927 
928 int32
929 BOutlineListView::_FullListIndex(int32 index) const
930 {
931 	BListItem* item = ItemAt(index);
932 
933 	if (item == NULL)
934 		return -1;
935 
936 	return FullListIndexOf(item);
937 }
938 
939 
940 void
941 BOutlineListView::_PopulateTree(BList* tree, BList& target,
942 	int32& firstIndex, bool onlyVisible)
943 {
944 	BListItem** items = (BListItem**)target.Items();
945 	int32 count = tree->CountItems();
946 
947 	for (int32 index = 0; index < count; index++) {
948 		BListItem* item = (BListItem*)tree->ItemAtFast(index);
949 
950 		items[firstIndex++] = item;
951 
952 		if (item->HasSubitems() && (!onlyVisible || item->IsExpanded())) {
953 			_PopulateTree(item->fTemporaryList, target, firstIndex,
954 				onlyVisible);
955 		}
956 	}
957 }
958 
959 
960 void
961 BOutlineListView::_SortTree(BList* tree, bool oneLevelOnly,
962 	int (*compareFunc)(const BListItem* a, const BListItem* b))
963 {
964 	BListItem** items = (BListItem**)tree->Items();
965 	std::sort(items, items + tree->CountItems(),
966 		ListItemComparator(compareFunc));
967 
968 	if (oneLevelOnly)
969 		return;
970 
971 	for (int32 index = tree->CountItems(); index-- > 0;) {
972 		BListItem* item = (BListItem*)tree->ItemAt(index);
973 
974 		if (item->HasSubitems())
975 			_SortTree(item->fTemporaryList, false, compareFunc);
976 	}
977 }
978 
979 
980 void
981 BOutlineListView::_DestructTree(BList* tree)
982 {
983 	for (int32 index = tree->CountItems(); index-- > 0;) {
984 		BListItem* item = (BListItem*)tree->ItemAt(index);
985 
986 		if (item->HasSubitems())
987 			_DestructTree(item->fTemporaryList);
988 	}
989 
990 	delete tree;
991 }
992 
993 
994 BList*
995 BOutlineListView::_BuildTree(BListItem* superItem, int32& fullListIndex)
996 {
997 	int32 fullCount = FullListCountItems();
998 	uint32 level = superItem != NULL ? superItem->OutlineLevel() + 1 : 0;
999 	BList* list = new BList;
1000 	if (superItem != NULL)
1001 		superItem->fTemporaryList = list;
1002 
1003 	while (fullListIndex < fullCount) {
1004 		BListItem* item = FullListItemAt(fullListIndex);
1005 
1006 		// If we jump out of the subtree, break out
1007 		if (item->fLevel < level)
1008 			break;
1009 
1010 		// If the level matches, put them into the list
1011 		// (we handle the case of a missing sublevel gracefully)
1012 		list->AddItem(item);
1013 		fullListIndex++;
1014 
1015 		if (item->HasSubitems()) {
1016 			// we're going deeper
1017 			_BuildTree(item, fullListIndex);
1018 		}
1019 	}
1020 
1021 	return list;
1022 }
1023 
1024 
1025 void
1026 BOutlineListView::_CullInvisibleItems(BList& list)
1027 {
1028 	int32 index = 0;
1029 	while (index < list.CountItems()) {
1030 		if (reinterpret_cast<BListItem*>(list.ItemAt(index))->IsItemVisible())
1031 			++index;
1032 		else
1033 			list.RemoveItem(index);
1034 	}
1035 }
1036 
1037 
1038 bool
1039 BOutlineListView::_SwapItems(int32 first, int32 second)
1040 {
1041 	// same item, do nothing
1042 	if (first == second)
1043 		return true;
1044 
1045 	// fail, first item out of bounds
1046 	if ((first < 0) || (first >= CountItems()))
1047 		return false;
1048 
1049 	// fail, second item out of bounds
1050 	if ((second < 0) || (second >= CountItems()))
1051 		return false;
1052 
1053 	int32 firstIndex = min_c(first, second);
1054 	int32 secondIndex = max_c(first, second);
1055 	BListItem* firstItem = ItemAt(firstIndex);
1056 	BListItem* secondItem = ItemAt(secondIndex);
1057 	BList firstSubItems, secondSubItems;
1058 
1059 	if (Superitem(firstItem) != Superitem(secondItem))
1060 		return false;
1061 	if (!firstItem->IsItemVisible() || !secondItem->IsItemVisible())
1062 		return false;
1063 
1064 	int32 fullFirstIndex = _FullListIndex(firstIndex);
1065 	int32 fullSecondIndex = _FullListIndex(secondIndex);
1066 	_GetSubItems(fFullList, firstSubItems, firstItem, fullFirstIndex + 1);
1067 	_GetSubItems(fFullList, secondSubItems, secondItem, fullSecondIndex + 1);
1068 	_DoSwap(fFullList, fullFirstIndex, fullSecondIndex, &firstSubItems,
1069 		&secondSubItems);
1070 
1071 	_CullInvisibleItems(firstSubItems);
1072 	_CullInvisibleItems(secondSubItems);
1073 	_DoSwap(fList, firstIndex, secondIndex, &firstSubItems,
1074 		&secondSubItems);
1075 
1076 	_RecalcItemTops(firstIndex);
1077 	_RescanSelection(firstIndex, secondIndex + secondSubItems.CountItems());
1078 	Invalidate(Bounds());
1079 	return true;
1080 }
1081 
1082 
1083 /*!	\brief Removes a single item from the list and all of its children.
1084 
1085 	Unlike the BeOS version, this one will actually delete the children, too,
1086 	as there should be no reference left to them. This may cause problems for
1087 	applications that actually take the misbehaviour of the Be classes into
1088 	account.
1089 */
1090 BListItem*
1091 BOutlineListView::_RemoveItem(BListItem* item, int32 fullListIndex)
1092 {
1093 	if (item == NULL || fullListIndex < 0
1094 		|| fullListIndex >= FullListCountItems()) {
1095 		return NULL;
1096 	}
1097 
1098 	uint32 level = item->OutlineLevel();
1099 	int32 superIndex;
1100 	BListItem* super = _SuperitemForIndex(fullListIndex, level, &superIndex);
1101 
1102 	if (item->IsItemVisible()) {
1103 		// remove children, too
1104 		while (fullListIndex + 1 < FullListCountItems()) {
1105 			BListItem* subItem = FullListItemAt(fullListIndex + 1);
1106 
1107 			if (subItem->OutlineLevel() <= level)
1108 				break;
1109 
1110 			if (subItem->IsItemVisible())
1111 				BListView::RemoveItem(subItem);
1112 
1113 			fFullList.RemoveItem(fullListIndex + 1);
1114 			delete subItem;
1115 		}
1116 		BListView::RemoveItem(item);
1117 	}
1118 
1119 	fFullList.RemoveItem(fullListIndex);
1120 
1121 	if (super != NULL) {
1122 		// we might need to change the fHasSubitems field of the parent
1123 		BListItem* child = FullListItemAt(superIndex + 1);
1124 		if (child == NULL || child->OutlineLevel() <= super->OutlineLevel())
1125 			super->fHasSubitems = false;
1126 	}
1127 	return item;
1128 }
1129 
1130 
1131 /*!	Returns the super item before the item specified by \a fullListIndex
1132 	and \a level.
1133 */
1134 BListItem*
1135 BOutlineListView::_SuperitemForIndex(int32 fullListIndex, int32 level,
1136 	int32* _superIndex)
1137 {
1138 	BListItem* item;
1139 	fullListIndex--;
1140 
1141 	while (fullListIndex >= 0) {
1142 		if ((item = FullListItemAt(fullListIndex))->OutlineLevel()
1143 				< (uint32)level) {
1144 			if (_superIndex != NULL)
1145 				*_superIndex = fullListIndex;
1146 			return item;
1147 		}
1148 
1149 		fullListIndex--;
1150 	}
1151 
1152 	return NULL;
1153 }
1154 
1155 
1156 int32
1157 BOutlineListView::_FindPreviousVisibleIndex(int32 fullListIndex)
1158 {
1159 	fullListIndex--;
1160 
1161 	while (fullListIndex >= 0) {
1162 		if (FullListItemAt(fullListIndex)->fVisible)
1163 			return fullListIndex;
1164 
1165 		fullListIndex--;
1166 	}
1167 
1168 	return -1;
1169 }
1170