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