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