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