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