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