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