xref: /haiku/src/kits/interface/ListView.cpp (revision ddac407426cd3b3d0b4589d7a161b300b3539a2a)
1 /*
2  * Copyright (c) 2001-2009, Haiku, Inc.
3  * Distributed under the terms of the MIT license.
4  *
5  * Authors:
6  *		Ulrich Wimboeck
7  *		Marc Flerackers (mflerackers@androme.be)
8  *		Stephan Assmus <superstippi@gmx.de>
9  *		Axel Dörfler, axeld@pinc-software.de
10  *		Rene Gollent (rene@gollent.com)
11  */
12 
13 
14 #include <ListView.h>
15 
16 #include <stdio.h>
17 
18 #include <Autolock.h>
19 #include <LayoutUtils.h>
20 #include <PropertyInfo.h>
21 #include <ScrollBar.h>
22 #include <ScrollView.h>
23 #include <Window.h>
24 
25 #include <binary_compatibility/Interface.h>
26 
27 
28 struct track_data {
29 	BPoint		drag_start;
30 	int32		item_index;
31 	bool		was_selected;
32 	bool		try_drag;
33 	bigtime_t	last_click_time;
34 };
35 
36 const float kDoubleClickTresh = 6;
37 
38 static property_info sProperties[] = {
39 	{ "Item", { B_COUNT_PROPERTIES, 0 }, { B_DIRECT_SPECIFIER, 0 },
40 		"Returns the number of BListItems currently in the list.", 0, { B_INT32_TYPE }
41 	},
42 
43 	{ "Item", { B_EXECUTE_PROPERTY, 0 }, { B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER,
44 		B_RANGE_SPECIFIER, B_REVERSE_RANGE_SPECIFIER, 0 },
45 		"Select and invoke the specified items, first removing any existing selection."
46 	},
47 
48 	{ "Selection", { B_COUNT_PROPERTIES, 0 }, { B_DIRECT_SPECIFIER, 0 },
49 		"Returns int32 count of items in the selection.", 0, { B_INT32_TYPE }
50 	},
51 
52 	{ "Selection", { B_EXECUTE_PROPERTY, 0 }, { B_DIRECT_SPECIFIER, 0 },
53 		"Invoke items in selection."
54 	},
55 
56 	{ "Selection", { B_GET_PROPERTY, 0 }, { B_DIRECT_SPECIFIER, 0 },
57 		"Returns int32 indices of all items in the selection.", 0, { B_INT32_TYPE }
58 	},
59 
60 	{ "Selection", { B_SET_PROPERTY, 0 }, { B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER,
61 		B_RANGE_SPECIFIER, B_REVERSE_RANGE_SPECIFIER, 0 },
62 		"Extends current selection or deselects specified items. Boolean field \"data\" "
63 		"chooses selection or deselection.", 0, { B_BOOL_TYPE }
64 	},
65 
66 	{ "Selection", { B_SET_PROPERTY, 0 }, { B_DIRECT_SPECIFIER, 0 },
67 		"Select or deselect all items in the selection. Boolean field \"data\" chooses "
68 		"selection or deselection.", 0, { B_BOOL_TYPE }
69 	},
70 };
71 
72 
73 BListView::BListView(BRect frame, const char* name, list_view_type type,
74 		uint32 resizingMode, uint32 flags)
75 	: BView(frame, name, resizingMode, flags)
76 {
77 	_InitObject(type);
78 }
79 
80 
81 BListView::BListView(const char* name, list_view_type type, uint32 flags)
82 	: BView(name, flags)
83 {
84 	_InitObject(type);
85 }
86 
87 
88 BListView::BListView(list_view_type type)
89 	: BView(NULL, B_WILL_DRAW | B_FRAME_EVENTS | B_NAVIGABLE)
90 {
91 	_InitObject(type);
92 }
93 
94 
95 BListView::BListView(BMessage* archive)
96 	: BView(archive)
97 {
98 	int32 listType;
99 	archive->FindInt32("_lv_type", &listType);
100 	_InitObject((list_view_type)listType);
101 
102 	int32 i = 0;
103 	BMessage subData;
104 	while (archive->FindMessage("_l_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 	if (archive->HasMessage("_msg")) {
115 		BMessage *invokationMessage = new BMessage;
116 
117 		archive->FindMessage("_msg", invokationMessage);
118 		SetInvocationMessage(invokationMessage);
119 	}
120 
121 	if (archive->HasMessage("_2nd_msg")) {
122 		BMessage *selectionMessage = new BMessage;
123 
124 		archive->FindMessage("_2nd_msg", selectionMessage);
125 		SetSelectionMessage(selectionMessage);
126 	}
127 }
128 
129 
130 BListView::~BListView()
131 {
132 	// NOTE: According to BeBook, BListView does not free the items itself.
133 	delete fTrack;
134 	SetSelectionMessage(NULL);
135 }
136 
137 
138 BArchivable*
139 BListView::Instantiate(BMessage* archive)
140 {
141 	if (validate_instantiation(archive, "BListView"))
142 		return new BListView(archive);
143 
144 	return NULL;
145 }
146 
147 
148 status_t
149 BListView::Archive(BMessage* archive, bool deep) const
150 {
151 	status_t status = BView::Archive(archive, deep);
152 	if (status < B_OK)
153 		return status;
154 
155 	status = archive->AddInt32("_lv_type", fListType);
156 	if (status == B_OK && deep) {
157 		BListItem *item;
158 		int32 i = 0;
159 
160 		while ((item = ItemAt(i++))) {
161 			BMessage subData;
162 			status = item->Archive(&subData, true);
163 			if (status >= B_OK)
164 				status = archive->AddMessage("_l_items", &subData);
165 
166 			if (status < B_OK)
167 				break;
168 		}
169 	}
170 
171 	if (status >= B_OK && InvocationMessage() != NULL)
172 		status = archive->AddMessage("_msg", InvocationMessage());
173 
174 	if (status == B_OK && fSelectMessage != NULL)
175 		status = archive->AddMessage("_2nd_msg", fSelectMessage);
176 
177 	return status;
178 }
179 
180 
181 void
182 BListView::Draw(BRect updateRect)
183 {
184 	int32 count = CountItems();
185 	if (count == 0)
186 		return;
187 
188 	BRect itemFrame(0, 0, Bounds().right, -1);
189 	for (int i = 0; i < count; i++) {
190 		BListItem* item = ItemAt(i);
191 		itemFrame.bottom = itemFrame.top + ceilf(item->Height()) - 1;
192 
193 		if (itemFrame.Intersects(updateRect))
194 			DrawItem(item, itemFrame);
195 
196 		itemFrame.top = itemFrame.bottom + 1;
197 	}
198 }
199 
200 
201 void
202 BListView::MessageReceived(BMessage* msg)
203 {
204 	switch (msg->what) {
205 		case B_COUNT_PROPERTIES:
206 		case B_EXECUTE_PROPERTY:
207 		case B_GET_PROPERTY:
208 		case B_SET_PROPERTY:
209 		{
210 			BPropertyInfo propInfo(sProperties);
211 			BMessage specifier;
212 			const char *property;
213 
214 			if (msg->GetCurrentSpecifier(NULL, &specifier) != B_OK
215 				|| specifier.FindString("property", &property) != B_OK)
216 				return;
217 
218 			switch (propInfo.FindMatch(msg, 0, &specifier, msg->what, property)) {
219 				case B_ERROR:
220 					BView::MessageReceived(msg);
221 					break;
222 
223 				case 0:
224 				{
225 					BMessage reply(B_REPLY);
226 					reply.AddInt32("result", CountItems());
227 					reply.AddInt32("error", B_OK);
228 
229 					msg->SendReply(&reply);
230 					break;
231 				}
232 
233 				case 1:
234 					break;
235 
236 				case 2:
237 				{
238 					int32 count = 0;
239 
240 					for (int32 i = 0; i < CountItems(); i++) {
241 						if (ItemAt(i)->IsSelected())
242 							count++;
243 					}
244 
245 					BMessage reply(B_REPLY);
246 					reply.AddInt32("result", count);
247 					reply.AddInt32("error", B_OK);
248 
249 					msg->SendReply(&reply);
250 					break;
251 				}
252 
253 				case 3:
254 					break;
255 
256 				case 4:
257 				{
258 					BMessage reply (B_REPLY);
259 
260 					for (int32 i = 0; i < CountItems(); i++) {
261 						if (ItemAt(i)->IsSelected())
262 							reply.AddInt32("result", i);
263 					}
264 
265 					reply.AddInt32("error", B_OK);
266 
267 					msg->SendReply(&reply);
268 					break;
269 				}
270 
271 				case 5:
272 					break;
273 
274 				case 6:
275 				{
276 					BMessage reply(B_REPLY);
277 
278 					bool select;
279 					if (msg->FindBool("data", &select) == B_OK && select)
280 						Select(0, CountItems() - 1, false);
281 					else
282 						DeselectAll();
283 
284 					reply.AddInt32("error", B_OK);
285 
286 					msg->SendReply(&reply);
287 					break;
288 				}
289 			}
290 			break;
291 		}
292 
293 		case B_SELECT_ALL:
294 			Select(0, CountItems() - 1, false);
295 			break;
296 
297 		default:
298 			BView::MessageReceived(msg);
299 	}
300 }
301 
302 
303 void
304 BListView::MouseDown(BPoint point)
305 {
306 	if (!IsFocus()) {
307 		MakeFocus();
308 		Sync();
309 		Window()->UpdateIfNeeded();
310 	}
311 
312 	BMessage *message = Looper()->CurrentMessage();
313 	int32 index = IndexOf(point);
314 
315 	// If the user double (or more) clicked within the current selection,
316 	// we don't change the selection but invoke the selection.
317 	// TODO: move this code someplace where it can be shared everywhere
318 	// instead of every class having to reimplement it, once some sane
319 	// API for it is decided.
320 	BPoint delta = point - fTrack->drag_start;
321 	bigtime_t sysTime;
322 	Window()->CurrentMessage()->FindInt64("when", &sysTime);
323 	bigtime_t timeDelta = sysTime - fTrack->last_click_time;
324 	bigtime_t doubleClickSpeed;
325 	get_click_speed(&doubleClickSpeed);
326 	bool doubleClick = false;
327 
328 	if (timeDelta < doubleClickSpeed
329 		&& fabs(delta.x) < kDoubleClickTresh
330 		&& fabs(delta.y) < kDoubleClickTresh
331 		&& fTrack->item_index == index)
332 		doubleClick = true;
333 
334 	if (doubleClick && index >= fFirstSelected && index <= fLastSelected) {
335 		fTrack->drag_start.Set(LONG_MAX, LONG_MAX);
336 		Invoke();
337 		return;
338 	}
339 
340 	int32 modifiers;
341 	message->FindInt32("modifiers", &modifiers);
342 
343 	if (!doubleClick) {
344 		fTrack->drag_start = point;
345 		fTrack->last_click_time = system_time();
346 		fTrack->item_index = index;
347 		fTrack->was_selected = index >= 0 ? ItemAt(index)->IsSelected() : false;
348 		fTrack->try_drag = true;
349 	}
350 
351 	if (index > -1) {
352 		if (fListType == B_MULTIPLE_SELECTION_LIST) {
353 			if (modifiers & B_SHIFT_KEY) {
354 				// select entire block
355 				// TODO: maybe review if we want it like in Tracker (anchor item)
356 				Select(min_c(index, fFirstSelected), max_c(index, fLastSelected));
357 			} else {
358 				if (modifiers & B_COMMAND_KEY) {
359 					// toggle selection state of clicked item (like in Tracker)
360 					// toggle selection state of clicked item
361 					if (ItemAt(index)->IsSelected())
362 						Deselect(index);
363 					else
364 						Select(index, true);
365 				} else {
366 					Select(index);
367 				}
368 			}
369 		} else {
370 			// toggle selection state of clicked item
371 			if ((modifiers & B_COMMAND_KEY) && ItemAt(index)->IsSelected())
372 				Deselect(index);
373 			else
374 				Select(index);
375 		}
376 	} else {
377 		if (!(modifiers & B_COMMAND_KEY))
378 			DeselectAll();
379 	}
380 }
381 
382 // MouseUp
383 void
384 BListView::MouseUp(BPoint pt)
385 {
386 	fTrack->try_drag = false;
387 }
388 
389 
390 void
391 BListView::MouseMoved(BPoint where, uint32 code, const BMessage* dragMessage)
392 {
393 	if (fTrack->item_index == -1 || !fTrack->try_drag) {
394 		// mouse was not clicked above any item
395 		// or no mouse button pressed
396 		return;
397 	}
398 
399 	// Initiate a drag if the mouse was moved far enough
400 	BPoint offset = where - fTrack->drag_start;
401 	float dragDistance = sqrtf(offset.x * offset.x + offset.y * offset.y);
402 	if (dragDistance >= 5.0f) {
403 		fTrack->try_drag = false;
404 		InitiateDrag(fTrack->drag_start, fTrack->item_index,
405 			fTrack->was_selected);
406 	}
407 }
408 
409 
410 void
411 BListView::KeyDown(const char *bytes, int32 numBytes)
412 {
413 	switch (bytes[0]) {
414 		case B_UP_ARROW:
415 		{
416 			if (fFirstSelected == -1) {
417 				// if nothing is selected yet, always select the first item
418 				Select(0);
419 			} else {
420 				bool extend = false;
421 				if (fListType == B_MULTIPLE_SELECTION_LIST
422 					&& (modifiers() & B_SHIFT_KEY) != 0)
423 					extend = true;
424 
425 				Select(fFirstSelected - 1, extend);
426 			}
427 
428 			ScrollToSelection();
429 			break;
430 		}
431 		case B_DOWN_ARROW:
432 		{
433 			if (fFirstSelected == -1) {
434 				// if nothing is selected yet, always select the first item
435 				Select(0);
436 			} else {
437 				bool extend = false;
438 				if (fListType == B_MULTIPLE_SELECTION_LIST
439 					&& (modifiers() & B_SHIFT_KEY) != 0)
440 					extend = true;
441 
442 				Select(fLastSelected + 1, extend);
443 			}
444 
445 			ScrollToSelection();
446 			break;
447 		}
448 
449 		case B_HOME:
450 			Select(0, fListType == B_MULTIPLE_SELECTION_LIST);
451 			ScrollToSelection();
452 			break;
453 		case B_END:
454 			Select(CountItems() - 1, fListType == B_MULTIPLE_SELECTION_LIST);
455 			ScrollToSelection();
456 			break;
457 
458 		case B_PAGE_UP:
459 		{
460 			BPoint scrollOffset(LeftTop());
461 			scrollOffset.y = max_c(0, scrollOffset.y - Bounds().Height());
462 			ScrollTo(scrollOffset);
463 			break;
464 		}
465 		case B_PAGE_DOWN:
466 		{
467 			BPoint scrollOffset(LeftTop());
468 			if (BListItem* item = LastItem()) {
469 				scrollOffset.y += Bounds().Height();
470 				scrollOffset.y = min_c(item->Bottom() - Bounds().Height(),
471 					scrollOffset.y);
472 			}
473 			ScrollTo(scrollOffset);
474 			break;
475 		}
476 
477 		case B_RETURN:
478 		case B_SPACE:
479 			Invoke();
480 			break;
481 
482 		default:
483 			BView::KeyDown(bytes, numBytes);
484 	}
485 }
486 
487 
488 void
489 BListView::MakeFocus(bool focused)
490 {
491 	if (IsFocus() == focused)
492 		return;
493 
494 	BView::MakeFocus(focused);
495 
496 	if (fScrollView)
497 		fScrollView->SetBorderHighlighted(focused);
498 }
499 
500 
501 void
502 BListView::AttachedToWindow()
503 {
504 	BView::AttachedToWindow();
505 	_FontChanged();
506 
507 	if (!Messenger().IsValid())
508 		SetTarget(Window(), NULL);
509 
510 	_FixupScrollBar();
511 }
512 
513 
514 void
515 BListView::FrameResized(float width, float height)
516 {
517 	_FixupScrollBar();
518 }
519 
520 
521 void
522 BListView::FrameMoved(BPoint new_position)
523 {
524 	BView::FrameMoved(new_position);
525 }
526 
527 
528 void
529 BListView::SetFont(const BFont* font, uint32 mask)
530 {
531 	BView::SetFont(font, mask);
532 	_FontChanged();
533 }
534 
535 
536 void
537 BListView::TargetedByScrollView(BScrollView *view)
538 {
539 	fScrollView = view;
540 }
541 
542 
543 void
544 BListView::ScrollTo(BPoint point)
545 {
546 	BView::ScrollTo(point);
547 }
548 
549 
550 bool
551 BListView::AddItem(BListItem *item, int32 index)
552 {
553 	if (!fList.AddItem(item, index))
554 		return false;
555 
556 	if (fFirstSelected != -1 && index <= fFirstSelected)
557 		fFirstSelected++;
558 
559 	if (fLastSelected != -1 && index <= fLastSelected)
560 		fLastSelected++;
561 
562 	if (Window()) {
563 		BFont font;
564 		GetFont(&font);
565 		item->SetTop((index > 0) ? ItemAt(index - 1)->Bottom() + 1.0 : 0.0);
566 
567 		item->Update(this, &font);
568 		_RecalcItemTops(index + 1);
569 
570 		_FixupScrollBar();
571 		_InvalidateFrom(index);
572 	}
573 
574 	return true;
575 }
576 
577 
578 bool
579 BListView::AddItem(BListItem* item)
580 {
581 	if (!fList.AddItem(item))
582 		return false;
583 
584 	// No need to adapt selection, as this item is the last in the list
585 
586 	if (Window()) {
587 		BFont font;
588 		GetFont(&font);
589 		int32 index = CountItems() - 1;
590 		item->SetTop((index > 0) ? ItemAt(index - 1)->Bottom() + 1.0 : 0.0);
591 
592 		item->Update(this, &font);
593 
594 		_FixupScrollBar();
595 		InvalidateItem(CountItems() - 1);
596 	}
597 
598 	return true;
599 }
600 
601 
602 bool
603 BListView::AddList(BList* list, int32 index)
604 {
605 	if (!fList.AddList(list, index))
606 		return false;
607 
608 	int32 count = fList.CountItems();
609 
610 	if (fFirstSelected != -1 && index < fFirstSelected)
611 		fFirstSelected += count;
612 
613 	if (fLastSelected != -1 && index < fLastSelected)
614 		fLastSelected += count;
615 
616 	if (Window()) {
617 		BFont font;
618 		GetFont(&font);
619 
620 		for (int32 i = index; i <= (index + list->CountItems() - 1); i++) {
621 			ItemAt(i)->SetTop((i > 0) ? ItemAt(i - 1)->Bottom() + 1.0 : 0.0);
622 			ItemAt(i)->Update(this, &font);
623 		}
624 
625 		_RecalcItemTops(index + list->CountItems() - 1);
626 
627 		_FixupScrollBar();
628 		Invalidate(); // TODO
629 	}
630 
631 	return true;
632 }
633 
634 
635 bool
636 BListView::AddList(BList* list)
637 {
638 	return AddList(list, CountItems());
639 }
640 
641 
642 BListItem*
643 BListView::RemoveItem(int32 index)
644 {
645 	BListItem *item = ItemAt(index);
646 	if (!item)
647 		return NULL;
648 
649 	if (item->IsSelected())
650 		Deselect(index);
651 
652 	if (!fList.RemoveItem(item))
653 		return NULL;
654 
655 	if (fFirstSelected != -1 && index < fFirstSelected)
656 		fFirstSelected--;
657 
658 	if (fLastSelected != -1 && index < fLastSelected)
659 		fLastSelected--;
660 
661 	_RecalcItemTops(index);
662 
663 	_InvalidateFrom(index);
664 	_FixupScrollBar();
665 
666 	return item;
667 }
668 
669 
670 bool
671 BListView::RemoveItem(BListItem *item)
672 {
673 	return BListView::RemoveItem(IndexOf(item)) != NULL;
674 }
675 
676 
677 bool
678 BListView::RemoveItems(int32 index, int32 count)
679 {
680 	if (index >= fList.CountItems())
681 		index = -1;
682 
683 	if (index < 0)
684 		return false;
685 
686 	fList.RemoveItems(index, count);
687 	if (index < fList.CountItems())
688 		_RecalcItemTops(index);
689 	Invalidate();
690 	return true;
691 }
692 
693 
694 void
695 BListView::SetSelectionMessage(BMessage* message)
696 {
697 	delete fSelectMessage;
698 	fSelectMessage = message;
699 }
700 
701 
702 void
703 BListView::SetInvocationMessage(BMessage* message)
704 {
705 	BInvoker::SetMessage(message);
706 }
707 
708 
709 BMessage*
710 BListView::InvocationMessage() const
711 {
712 	return BInvoker::Message();
713 }
714 
715 
716 uint32
717 BListView::InvocationCommand() const
718 {
719 	return BInvoker::Command();
720 }
721 
722 
723 BMessage*
724 BListView::SelectionMessage() const
725 {
726 	return fSelectMessage;
727 }
728 
729 
730 uint32
731 BListView::SelectionCommand() const
732 {
733 	if (fSelectMessage)
734 		return fSelectMessage->what;
735 
736 	return 0;
737 }
738 
739 
740 void
741 BListView::SetListType(list_view_type type)
742 {
743 	if (fListType == B_MULTIPLE_SELECTION_LIST &&
744 		type == B_SINGLE_SELECTION_LIST)
745 		Select(CurrentSelection(0));
746 
747 	fListType = type;
748 }
749 
750 
751 list_view_type
752 BListView::ListType() const
753 {
754 	return fListType;
755 }
756 
757 
758 BListItem*
759 BListView::ItemAt(int32 index) const
760 {
761 	return (BListItem*)fList.ItemAt(index);
762 }
763 
764 
765 int32
766 BListView::IndexOf(BListItem *item) const
767 {
768 	if (Window()) {
769 		if (item != NULL) {
770 			int32 index = IndexOf(BPoint(0.0, item->Top()));
771 			if (index >= 0 && fList.ItemAt(index) == item)
772 				return index;
773 			return -1;
774 		}
775 	}
776 	return fList.IndexOf(item);
777 }
778 
779 
780 int32
781 BListView::IndexOf(BPoint point) const
782 {
783 	int32 low = 0;
784 	int32 high = fList.CountItems() - 1;
785 	int32 mid = -1;
786 	float frameTop = -1.0;
787 	float frameBottom = 1.0;
788 	// binary search the list
789 	while (high >= low) {
790 		mid = (low + high) / 2;
791 		frameTop = ItemAt(mid)->Top();
792 		frameBottom = ItemAt(mid)->Bottom();
793 		if (point.y < frameTop)
794 			high = mid - 1;
795 		else if (point.y > frameBottom)
796 			low = mid + 1;
797 		else
798 			return mid;
799 	}
800 
801 	return -1;
802 }
803 
804 
805 BListItem*
806 BListView::FirstItem() const
807 {
808 	return (BListItem*)fList.FirstItem();
809 }
810 
811 
812 BListItem*
813 BListView::LastItem() const
814 {
815 	return (BListItem*)fList.LastItem();
816 }
817 
818 
819 bool
820 BListView::HasItem(BListItem *item) const
821 {
822 	return IndexOf(item) != -1;
823 }
824 
825 
826 int32
827 BListView::CountItems() const
828 {
829 	return fList.CountItems();
830 }
831 
832 
833 void
834 BListView::MakeEmpty()
835 {
836 	if (fList.IsEmpty())
837 		return;
838 
839 	_DeselectAll(-1, -1);
840 	fList.MakeEmpty();
841 
842 	if (Window()) {
843 		_FixupScrollBar();
844 		Invalidate();
845 	}
846 }
847 
848 
849 bool
850 BListView::IsEmpty() const
851 {
852 	return fList.IsEmpty();
853 }
854 
855 
856 void
857 BListView::DoForEach(bool (*func)(BListItem*))
858 {
859 	fList.DoForEach(reinterpret_cast<bool (*)(void*)>(func));
860 }
861 
862 
863 void
864 BListView::DoForEach(bool (*func)(BListItem*, void*), void* arg)
865 {
866 	fList.DoForEach(reinterpret_cast<bool (*)(void*, void*)>(func), arg);
867 }
868 
869 
870 const BListItem**
871 BListView::Items() const
872 {
873 	return (const BListItem**)fList.Items();
874 }
875 
876 
877 void
878 BListView::InvalidateItem(int32 index)
879 {
880 	Invalidate(ItemFrame(index));
881 }
882 
883 
884 void
885 BListView::ScrollToSelection()
886 {
887 	BRect itemFrame = ItemFrame(CurrentSelection(0));
888 
889 	if (Bounds().Contains(itemFrame))
890 		return;
891 
892 	if (itemFrame.top < Bounds().top)
893 		ScrollTo(itemFrame.left, itemFrame.top);
894 	else
895 		ScrollTo(itemFrame.left, itemFrame.bottom - Bounds().Height());
896 }
897 
898 
899 void
900 BListView::Select(int32 index, bool extend)
901 {
902 	if (_Select(index, extend)) {
903 		SelectionChanged();
904 		InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED);
905 	}
906 }
907 
908 
909 void
910 BListView::Select(int32 start, int32 finish, bool extend)
911 {
912 	if (_Select(start, finish, extend)) {
913 		SelectionChanged();
914 		InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED);
915 	}
916 }
917 
918 
919 bool
920 BListView::IsItemSelected(int32 index) const
921 {
922 	BListItem *item = ItemAt(index);
923 	if (item)
924 		return item->IsSelected();
925 
926 	return false;
927 }
928 
929 
930 int32
931 BListView::CurrentSelection(int32 index) const
932 {
933 	if (fFirstSelected == -1)
934 		return -1;
935 
936 	if (index == 0)
937 		return fFirstSelected;
938 
939 	for (int32 i = fFirstSelected; i <= fLastSelected; i++) {
940 		if (ItemAt(i)->IsSelected()) {
941 			if (index == 0)
942 				return i;
943 
944 			index--;
945 		}
946 	}
947 
948 	return -1;
949 }
950 
951 
952 status_t
953 BListView::Invoke(BMessage* message)
954 {
955 	// Note, this is more or less a copy of BControl::Invoke() and should
956 	// stay that way (ie. changes done there should be adopted here)
957 
958 	bool notify = false;
959 	uint32 kind = InvokeKind(&notify);
960 
961 	BMessage clone(kind);
962 	status_t err = B_BAD_VALUE;
963 
964 	if (!message && !notify)
965 		message = Message();
966 
967 	if (!message) {
968 		if (!IsWatched())
969 			return err;
970 	} else
971 		clone = *message;
972 
973 	clone.AddInt64("when", (int64)system_time());
974 	clone.AddPointer("source", this);
975 	clone.AddMessenger("be:sender", BMessenger(this));
976 
977 	if (fListType == B_SINGLE_SELECTION_LIST)
978 		clone.AddInt32("index", fFirstSelected);
979 	else {
980 		if (fFirstSelected >= 0) {
981 			for (int32 i = fFirstSelected; i <= fLastSelected; i++) {
982 				if (ItemAt(i)->IsSelected())
983 					clone.AddInt32("index", i);
984 			}
985 		}
986 	}
987 
988 	if (message)
989 		err = BInvoker::Invoke(&clone);
990 
991 	SendNotices(kind, &clone);
992 
993 	return err;
994 }
995 
996 
997 void
998 BListView::DeselectAll()
999 {
1000 	if (_DeselectAll(-1, -1)) {
1001 		SelectionChanged();
1002 		InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED);
1003 	}
1004 }
1005 
1006 
1007 void
1008 BListView::DeselectExcept(int32 exceptFrom, int32 exceptTo)
1009 {
1010 	if (exceptFrom > exceptTo || exceptFrom < 0 || exceptTo < 0)
1011 		return;
1012 
1013 	if (_DeselectAll(exceptFrom, exceptTo)) {
1014 		SelectionChanged();
1015 		InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED);
1016 	}
1017 }
1018 
1019 
1020 void
1021 BListView::Deselect(int32 index)
1022 {
1023 	if (_Deselect(index)) {
1024 		SelectionChanged();
1025 		InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED);
1026 	}
1027 }
1028 
1029 
1030 void
1031 BListView::SelectionChanged()
1032 {
1033 	// Hook method to be implemented by subclasses
1034 }
1035 
1036 
1037 void
1038 BListView::SortItems(int (*cmp)(const void *, const void *))
1039 {
1040 	if (_DeselectAll(-1, -1)) {
1041 		SelectionChanged();
1042 		InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED);
1043 	}
1044 
1045 	fList.SortItems(cmp);
1046 	_RecalcItemTops(0);
1047 	Invalidate();
1048 }
1049 
1050 
1051 bool
1052 BListView::SwapItems(int32 a, int32 b)
1053 {
1054 	MiscData data;
1055 
1056 	data.swap.a = a;
1057 	data.swap.b = b;
1058 
1059 	return DoMiscellaneous(B_SWAP_OP, &data);
1060 }
1061 
1062 
1063 bool
1064 BListView::MoveItem(int32 from, int32 to)
1065 {
1066 	MiscData data;
1067 
1068 	data.move.from = from;
1069 	data.move.to = to;
1070 
1071 	return DoMiscellaneous(B_MOVE_OP, &data);
1072 }
1073 
1074 
1075 bool
1076 BListView::ReplaceItem(int32 index, BListItem *item)
1077 {
1078 	MiscData data;
1079 
1080 	data.replace.index = index;
1081 	data.replace.item = item;
1082 
1083 	return DoMiscellaneous(B_REPLACE_OP, &data);
1084 }
1085 
1086 
1087 BRect
1088 BListView::ItemFrame(int32 index)
1089 {
1090 	BRect frame = Bounds();
1091 	if (index < 0 || index >= CountItems()) {
1092 		frame.top = 0;
1093 		frame.bottom = -1;
1094 	} else {
1095 		BListItem* item = ItemAt(index);
1096 		frame.top = item->Top();
1097 		frame.bottom = item->Bottom();
1098 	}
1099 	return frame;
1100 }
1101 
1102 
1103 BHandler*
1104 BListView::ResolveSpecifier(BMessage* message, int32 index,
1105 	BMessage* specifier, int32 form, const char* property)
1106 {
1107 	BPropertyInfo propInfo(sProperties);
1108 
1109 	if (propInfo.FindMatch(message, 0, specifier, form, property) < 0) {
1110 		return BView::ResolveSpecifier(message, index, specifier, form,
1111 			property);
1112 	}
1113 
1114 	// TODO: msg->AddInt32("_match_code_", );
1115 
1116 	return this;
1117 }
1118 
1119 
1120 status_t
1121 BListView::GetSupportedSuites(BMessage* data)
1122 {
1123 	if (data == NULL)
1124 		return B_BAD_VALUE;
1125 
1126 	status_t err = data->AddString("suites", "suite/vnd.Be-list-view");
1127 
1128 	BPropertyInfo propertyInfo(sProperties);
1129 	if (err == B_OK)
1130 		err = data->AddFlat("messages", &propertyInfo);
1131 
1132 	if (err == B_OK)
1133 		return BView::GetSupportedSuites(data);
1134 	return err;
1135 }
1136 
1137 
1138 status_t
1139 BListView::Perform(perform_code code, void* _data)
1140 {
1141 	switch (code) {
1142 		case PERFORM_CODE_MIN_SIZE:
1143 			((perform_data_min_size*)_data)->return_value
1144 				= BListView::MinSize();
1145 			return B_OK;
1146 		case PERFORM_CODE_MAX_SIZE:
1147 			((perform_data_max_size*)_data)->return_value
1148 				= BListView::MaxSize();
1149 			return B_OK;
1150 		case PERFORM_CODE_PREFERRED_SIZE:
1151 			((perform_data_preferred_size*)_data)->return_value
1152 				= BListView::PreferredSize();
1153 			return B_OK;
1154 		case PERFORM_CODE_LAYOUT_ALIGNMENT:
1155 			((perform_data_layout_alignment*)_data)->return_value
1156 				= BListView::LayoutAlignment();
1157 			return B_OK;
1158 		case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH:
1159 			((perform_data_has_height_for_width*)_data)->return_value
1160 				= BListView::HasHeightForWidth();
1161 			return B_OK;
1162 		case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH:
1163 		{
1164 			perform_data_get_height_for_width* data
1165 				= (perform_data_get_height_for_width*)_data;
1166 			BListView::GetHeightForWidth(data->width, &data->min, &data->max,
1167 				&data->preferred);
1168 			return B_OK;
1169 		}
1170 		case PERFORM_CODE_SET_LAYOUT:
1171 		{
1172 			perform_data_set_layout* data = (perform_data_set_layout*)_data;
1173 			BListView::SetLayout(data->layout);
1174 			return B_OK;
1175 		}
1176 		case PERFORM_CODE_INVALIDATE_LAYOUT:
1177 		{
1178 			perform_data_invalidate_layout* data
1179 				= (perform_data_invalidate_layout*)_data;
1180 			BListView::InvalidateLayout(data->descendants);
1181 			return B_OK;
1182 		}
1183 		case PERFORM_CODE_DO_LAYOUT:
1184 		{
1185 			BListView::DoLayout();
1186 			return B_OK;
1187 		}
1188 	}
1189 
1190 	return BView::Perform(code, _data);
1191 }
1192 
1193 
1194 void
1195 BListView::WindowActivated(bool state)
1196 {
1197 	BView::WindowActivated(state);
1198 }
1199 
1200 
1201 void
1202 BListView::DetachedFromWindow()
1203 {
1204 	BView::DetachedFromWindow();
1205 }
1206 
1207 
1208 bool
1209 BListView::InitiateDrag(BPoint point, int32 index, bool wasSelected)
1210 {
1211 	return false;
1212 }
1213 
1214 
1215 void
1216 BListView::ResizeToPreferred()
1217 {
1218 	BView::ResizeToPreferred();
1219 }
1220 
1221 
1222 void
1223 BListView::GetPreferredSize(float* _width, float* _height)
1224 {
1225 	int32 count = CountItems();
1226 
1227 	if (count > 0) {
1228 		float maxWidth = 0.0;
1229 		for (int32 i = 0; i < count; i++) {
1230 			float itemWidth = ItemAt(i)->Width();
1231 			if (itemWidth > maxWidth)
1232 				maxWidth = itemWidth;
1233 		}
1234 
1235 		if (_width != NULL)
1236 			*_width = maxWidth;
1237 		if (_height != NULL)
1238 			*_height = ItemAt(count - 1)->Bottom();
1239 	} else {
1240 		BView::GetPreferredSize(_width, _height);
1241 	}
1242 }
1243 
1244 
1245 void
1246 BListView::AllAttached()
1247 {
1248 	BView::AllAttached();
1249 }
1250 
1251 
1252 void
1253 BListView::AllDetached()
1254 {
1255 	BView::AllDetached();
1256 }
1257 
1258 
1259 BSize
1260 BListView::MinSize()
1261 {
1262 	// We need a stable min size: the BView implementation uses
1263 	// GetPreferredSize(), which by default just returns the current size.
1264 	return BLayoutUtils::ComposeSize(ExplicitMinSize(), BSize(10, 10));
1265 }
1266 
1267 
1268 BSize
1269 BListView::MaxSize()
1270 {
1271 	return BView::MaxSize();
1272 }
1273 
1274 
1275 BSize
1276 BListView::PreferredSize()
1277 {
1278 	// We need a stable preferred size: the BView implementation uses
1279 	// GetPreferredSize(), which by default just returns the current size.
1280 	return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), BSize(100, 50));
1281 }
1282 
1283 
1284 bool
1285 BListView::DoMiscellaneous(MiscCode code, MiscData* data)
1286 {
1287 	if (code > B_SWAP_OP)
1288 		return false;
1289 
1290 	switch (code) {
1291 		case B_NO_OP:
1292 			break;
1293 
1294 		case B_REPLACE_OP:
1295 			return _ReplaceItem(data->replace.index, data->replace.item);
1296 
1297 		case B_MOVE_OP:
1298 			return _MoveItem(data->move.from, data->move.to);
1299 
1300 		case B_SWAP_OP:
1301 			return _SwapItems(data->swap.a, data->swap.b);
1302 	}
1303 
1304 	return false;
1305 }
1306 
1307 
1308 void BListView::_ReservedListView2() {}
1309 void BListView::_ReservedListView3() {}
1310 void BListView::_ReservedListView4() {}
1311 
1312 
1313 BListView&
1314 BListView::operator=(const BListView& /*other*/)
1315 {
1316 	return *this;
1317 }
1318 
1319 
1320 void
1321 BListView::_InitObject(list_view_type type)
1322 {
1323 	fListType = type;
1324 	fFirstSelected = -1;
1325 	fLastSelected = -1;
1326 	fAnchorIndex = -1;
1327 	fSelectMessage = NULL;
1328 	fScrollView = NULL;
1329 	fTrack = new track_data;
1330 	fTrack->try_drag = false;
1331 	fTrack->item_index = -1;
1332 }
1333 
1334 
1335 void
1336 BListView::_FixupScrollBar()
1337 {
1338 	BScrollBar* vertScroller = ScrollBar(B_VERTICAL);
1339 	if (!vertScroller)
1340 		return;
1341 
1342 	BRect bounds = Bounds();
1343 	int32 count = CountItems();
1344 
1345 	float itemHeight = 0.0;
1346 
1347 	if (CountItems() > 0)
1348 		itemHeight = ItemAt(CountItems() - 1)->Bottom();
1349 
1350 	if (bounds.Height() > itemHeight) {
1351 		// no scrolling
1352 		vertScroller->SetRange(0.0, 0.0);
1353 		vertScroller->SetValue(0.0);
1354 			// also scrolls ListView to the top
1355 	} else {
1356 		vertScroller->SetRange(0.0, itemHeight - bounds.Height() - 1.0);
1357 		vertScroller->SetProportion(bounds.Height () / itemHeight);
1358 		// scroll up if there is empty room on bottom
1359 		if (itemHeight < bounds.bottom)
1360 			ScrollBy(0.0, bounds.bottom - itemHeight);
1361 	}
1362 
1363 	if (count != 0)
1364 		vertScroller->SetSteps(ceilf(FirstItem()->Height()), bounds.Height());
1365 }
1366 
1367 
1368 void
1369 BListView::_InvalidateFrom(int32 index)
1370 {
1371 	// make sure index is behind last valid index
1372 	int32 count = CountItems();
1373 	if (index >= count)
1374 		index = count;
1375 
1376 	// take the item before the wanted one,
1377 	// because that might already be removed
1378 	index--;
1379 	BRect dirty = Bounds();
1380 	if (index >= 0)
1381 		dirty.top = ItemFrame(index).bottom + 1;
1382 
1383 	Invalidate(dirty);
1384 }
1385 
1386 
1387 void
1388 BListView::_FontChanged()
1389 {
1390 	BFont font;
1391 	GetFont(&font);
1392 	for (int32 i = 0; i < CountItems(); i++) {
1393 		ItemAt(i)->SetTop((i > 0) ? ItemAt(i - 1)->Bottom() + 1.0 : 0.0);
1394 		ItemAt(i)->Update(this, &font);
1395 	}
1396 }
1397 
1398 
1399 /*!	Selects the item at the specified \a index, and returns \c true in
1400 	case the selection was changed because of this method.
1401 	If \a extend is \c false, all previously selected items are deselected.
1402 */
1403 bool
1404 BListView::_Select(int32 index, bool extend)
1405 {
1406 	if (index < 0 || index >= CountItems())
1407 		return false;
1408 
1409 	// only lock the window when there is one
1410 	BAutolock locker(Window());
1411 	if (Window() != NULL && !locker.IsLocked())
1412 		return false;
1413 
1414 	bool changed = false;
1415 
1416 	if (fFirstSelected != -1 && !extend)
1417 		changed = _DeselectAll(index, index);
1418 
1419 	BListItem* item = ItemAt(index);
1420 	if (!item->IsEnabled() || item->IsSelected()) {
1421 		// if the item is already selected, or can't be selected,
1422 		// we're done here
1423 		return changed;
1424 	}
1425 
1426 	// keep track of first and last selected item
1427 	if (fFirstSelected == -1) {
1428 		// no previous selection
1429 		fFirstSelected = index;
1430 		fLastSelected = index;
1431 	} else if (index < fFirstSelected) {
1432 		fFirstSelected = index;
1433 	} else if (index > fLastSelected) {
1434 		fLastSelected = index;
1435 	}
1436 
1437 	ItemAt(index)->Select();
1438 	if (Window())
1439 		InvalidateItem(index);
1440 
1441 	return true;
1442 }
1443 
1444 
1445 /*!	Selects the items between \a from and \a to, and returns \c true in
1446 	case the selection was changed because of this method.
1447 	If \a extend is \c false, all previously selected items are deselected.
1448 */
1449 bool
1450 BListView::_Select(int32 from, int32 to, bool extend)
1451 {
1452 	if (to < from)
1453 		return false;
1454 
1455 	BAutolock locker(Window());
1456 	if (Window() && !locker.IsLocked())
1457 		return false;
1458 
1459 	bool changed = false;
1460 
1461 	if (fFirstSelected != -1 && !extend)
1462 		changed = _DeselectAll(from, to);
1463 
1464 	if (fFirstSelected == -1) {
1465 		fFirstSelected = from;
1466 		fLastSelected = to;
1467 	} else if (from < fFirstSelected)
1468 		fFirstSelected = from;
1469 	else if (to > fLastSelected)
1470 		fLastSelected = to;
1471 
1472 	for (int32 i = from; i <= to; ++i) {
1473 		BListItem *item = ItemAt(i);
1474 		if (item && !item->IsSelected()) {
1475 			item->Select();
1476 			if (Window())
1477 				InvalidateItem(i);
1478 			changed = true;
1479 		}
1480 	}
1481 
1482 	return changed;
1483 }
1484 
1485 
1486 bool
1487 BListView::_Deselect(int32 index)
1488 {
1489 	if (index < 0 || index >= CountItems())
1490 		return false;
1491 
1492 	BWindow *window = Window();
1493 	BAutolock locker(window);
1494 	if (window && !locker.IsLocked())
1495 		return false;
1496 
1497 	BListItem *item = ItemAt(index);
1498 
1499 	if (item && item->IsSelected()) {
1500 		BRect frame(ItemFrame(index));
1501 		BRect bounds(Bounds());
1502 
1503 		item->Deselect();
1504 
1505 		if (fFirstSelected == index && fLastSelected == index) {
1506 			fFirstSelected = -1;
1507 			fLastSelected = -1;
1508 		} else {
1509 			if (fFirstSelected == index)
1510 				fFirstSelected = _CalcFirstSelected(index);
1511 
1512 			if (fLastSelected == index)
1513 				fLastSelected = _CalcLastSelected(index);
1514 		}
1515 
1516 		if (window && bounds.Intersects(frame))
1517 			DrawItem(ItemAt(index), frame, true);
1518 	}
1519 
1520 	return true;
1521 }
1522 
1523 
1524 bool
1525 BListView::_DeselectAll(int32 exceptFrom, int32 exceptTo)
1526 {
1527 	if (fFirstSelected == -1)
1528 		return false;
1529 
1530 	BAutolock locker(Window());
1531 	if (Window() && !locker.IsLocked())
1532 		return false;
1533 
1534 	bool changed = false;
1535 
1536 	for (int32 index = fFirstSelected; index <= fLastSelected; index++) {
1537 		// don't deselect the items we shouldn't deselect
1538 		if (exceptFrom != -1 && exceptFrom <= index && exceptTo >= index)
1539 			continue;
1540 
1541 		BListItem *item = ItemAt(index);
1542 		if (item && item->IsSelected()) {
1543 			item->Deselect();
1544 			InvalidateItem(index);
1545 			changed = true;
1546 		}
1547 	}
1548 
1549 	if (!changed)
1550 		return false;
1551 
1552 	if (exceptFrom != -1) {
1553 		fFirstSelected = _CalcFirstSelected(exceptFrom);
1554 		fLastSelected = _CalcLastSelected(exceptTo);
1555 	} else
1556 		fFirstSelected = fLastSelected = -1;
1557 
1558 	return true;
1559 }
1560 
1561 
1562 int32
1563 BListView::_CalcFirstSelected(int32 after)
1564 {
1565 	if (after >= CountItems())
1566 		return -1;
1567 
1568 	int32 count = CountItems();
1569 	for (int32 i = after; i < count; i++) {
1570 		if (ItemAt(i)->IsSelected())
1571 			return i;
1572 	}
1573 
1574 	return -1;
1575 }
1576 
1577 
1578 int32
1579 BListView::_CalcLastSelected(int32 before)
1580 {
1581 	if (before < 0)
1582 		return -1;
1583 
1584 	before = min_c(CountItems() - 1, before);
1585 
1586 	for (int32 i = before; i >= 0; i--) {
1587 		if (ItemAt(i)->IsSelected())
1588 			return i;
1589 	}
1590 
1591 	return -1;
1592 }
1593 
1594 
1595 void
1596 BListView::DrawItem(BListItem* item, BRect itemRect, bool complete)
1597 {
1598 	item->DrawItem(this, itemRect, complete);
1599 }
1600 
1601 
1602 bool
1603 BListView::_SwapItems(int32 a, int32 b)
1604 {
1605 	// remember frames of items before anyhing happens,
1606 	// the tricky situation is when the two items have
1607 	// a different height
1608 	BRect aFrame = ItemFrame(a);
1609 	BRect bFrame = ItemFrame(b);
1610 
1611 	if (!fList.SwapItems(a, b))
1612 		return false;
1613 
1614 	if (a == b) {
1615 		// nothing to do, but success nevertheless
1616 		return true;
1617 	}
1618 
1619 	// track anchor item
1620 	if (fAnchorIndex == a)
1621 		fAnchorIndex = b;
1622 	else if (fAnchorIndex == b)
1623 		fAnchorIndex = a;
1624 
1625 	// track selection
1626 	// NOTE: this is only important if the selection status
1627 	// of both items is not the same
1628 	int32 first = min_c(a, b);
1629 	int32 last = max_c(a, b);
1630 	if (ItemAt(a)->IsSelected() != ItemAt(b)->IsSelected()) {
1631 		if (first < fFirstSelected || last > fLastSelected)
1632 			_RescanSelection(min_c(first, fFirstSelected), min_c(last, fLastSelected));
1633 		// though the actually selected items stayed the
1634 		// same, the selection has still changed
1635 		SelectionChanged();
1636 	}
1637 
1638 	ItemAt(a)->SetTop(bFrame.top);
1639 	ItemAt(b)->SetTop(aFrame.top);
1640 
1641 	// take care of invalidation
1642 	if (Window()) {
1643 		// NOTE: window looper is assumed to be locked!
1644 		if (aFrame.Height() != bFrame.Height()) {
1645 			_RecalcItemTops(first, last);
1646 			// items in between shifted visually
1647 			Invalidate(aFrame | bFrame);
1648 		} else {
1649 			Invalidate(aFrame);
1650 			Invalidate(bFrame);
1651 		}
1652 	}
1653 
1654 	return true;
1655 }
1656 
1657 
1658 bool
1659 BListView::_MoveItem(int32 from, int32 to)
1660 {
1661 	// remember item frames before doing anything
1662 	BRect frameFrom = ItemFrame(from);
1663 	BRect frameTo = ItemFrame(to);
1664 
1665 	if (!fList.MoveItem(from, to))
1666 		return false;
1667 
1668 	// track anchor item
1669 	if (fAnchorIndex == from)
1670 		fAnchorIndex = to;
1671 
1672 	// track selection
1673 	if (ItemAt(to)->IsSelected()) {
1674 		_RescanSelection(from, to);
1675 		// though the actually selected items stayed the
1676 		// same, the selection has still changed
1677 		SelectionChanged();
1678 	}
1679 
1680 	_RecalcItemTops((to > from) ? from : to);
1681 
1682 	// take care of invalidation
1683 	if (Window()) {
1684 		// NOTE: window looper is assumed to be locked!
1685 		Invalidate(frameFrom | frameTo);
1686 	}
1687 
1688 	return true;
1689 }
1690 
1691 
1692 bool
1693 BListView::_ReplaceItem(int32 index, BListItem *item)
1694 {
1695 	if (!item)
1696 		return false;
1697 
1698 	BListItem* old = ItemAt(index);
1699 	if (!old)
1700 		return false;
1701 
1702 	BRect frame = ItemFrame(index);
1703 
1704 	bool selectionChanged = old->IsSelected() != item->IsSelected();
1705 
1706 	// replace item
1707 	if (!fList.ReplaceItem(index, item))
1708 		return false;
1709 
1710 	// tack selection
1711 	if (selectionChanged) {
1712 		int32 start = min_c(fFirstSelected, index);
1713 		int32 end = max_c(fLastSelected, index);
1714 		_RescanSelection(start, end);
1715 		SelectionChanged();
1716 	}
1717 	_RecalcItemTops(index);
1718 
1719 	bool itemHeightChanged = frame != ItemFrame(index);
1720 
1721 	// take care of invalidation
1722 	if (Window()) {
1723 		// NOTE: window looper is assumed to be locked!
1724 		if (itemHeightChanged)
1725 			_InvalidateFrom(index);
1726 		else
1727 			Invalidate(frame);
1728 	}
1729 
1730 	if (itemHeightChanged)
1731 		_FixupScrollBar();
1732 
1733 	return true;
1734 }
1735 
1736 
1737 void
1738 BListView::_RescanSelection(int32 from, int32 to)
1739 {
1740 	if (from > to) {
1741 		int32 tmp = from;
1742 		from = to;
1743 		to = tmp;
1744 	}
1745 
1746 	from = max_c(0, from);
1747 	to = min_c(to, CountItems() - 1);
1748 
1749 	if (fAnchorIndex != -1) {
1750 		if (fAnchorIndex == from)
1751 			fAnchorIndex = to;
1752 		else if (fAnchorIndex == to)
1753 			fAnchorIndex = from;
1754 	}
1755 
1756 	for (int32 i = from; i <= to; i++) {
1757 		if (ItemAt(i)->IsSelected()) {
1758 			fFirstSelected = i;
1759 			break;
1760 		}
1761 	}
1762 
1763 	if (fFirstSelected > from)
1764 		from = fFirstSelected;
1765 	for (int32 i = from; i <= to; i++) {
1766 		if (ItemAt(i)->IsSelected())
1767 			fLastSelected = i;
1768 	}
1769 }
1770 
1771 
1772 void
1773 BListView::_RecalcItemTops(int32 start, int32 end)
1774 {
1775 	int32 count = CountItems();
1776 	if ((start < 0) || (start >= count))
1777 		return;
1778 
1779 	if (end >= 0)
1780 		count = end + 1;
1781 
1782 	float top = (start == 0) ? 0.0 : ItemAt(start - 1)->Bottom() + 1.0;
1783 
1784 	for (int32 i = start; i < count; i++) {
1785 		BListItem *item = ItemAt(i);
1786 		item->SetTop(top);
1787 		top += ceilf(item->Height());
1788 	}
1789 }
1790 
1791