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