xref: /haiku/src/kits/interface/ListView.cpp (revision a085e81e62d7a860f809b4fb7c7bf5654c396985)
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 	if (itemFrame.top < Bounds().top)
1041 		ScrollTo(itemFrame.left, itemFrame.top);
1042 	else
1043 		ScrollTo(itemFrame.left, itemFrame.bottom - Bounds().Height());
1044 }
1045 
1046 
1047 void
1048 BListView::Select(int32 index, bool extend)
1049 {
1050 	if (_Select(index, extend)) {
1051 		SelectionChanged();
1052 		InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED);
1053 	}
1054 }
1055 
1056 
1057 void
1058 BListView::Select(int32 start, int32 finish, bool extend)
1059 {
1060 	if (_Select(start, finish, extend)) {
1061 		SelectionChanged();
1062 		InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED);
1063 	}
1064 }
1065 
1066 
1067 bool
1068 BListView::IsItemSelected(int32 index) const
1069 {
1070 	BListItem* item = ItemAt(index);
1071 	if (item != NULL)
1072 		return item->IsSelected();
1073 
1074 	return false;
1075 }
1076 
1077 
1078 int32
1079 BListView::CurrentSelection(int32 index) const
1080 {
1081 	if (fFirstSelected == -1)
1082 		return -1;
1083 
1084 	if (index == 0)
1085 		return fFirstSelected;
1086 
1087 	for (int32 i = fFirstSelected; i <= fLastSelected; i++) {
1088 		if (ItemAt(i)->IsSelected()) {
1089 			if (index == 0)
1090 				return i;
1091 
1092 			index--;
1093 		}
1094 	}
1095 
1096 	return -1;
1097 }
1098 
1099 
1100 status_t
1101 BListView::Invoke(BMessage* message)
1102 {
1103 	// Note, this is more or less a copy of BControl::Invoke() and should
1104 	// stay that way (ie. changes done there should be adopted here)
1105 
1106 	bool notify = false;
1107 	uint32 kind = InvokeKind(&notify);
1108 
1109 	BMessage clone(kind);
1110 	status_t err = B_BAD_VALUE;
1111 
1112 	if (!message && !notify)
1113 		message = Message();
1114 
1115 	if (!message) {
1116 		if (!IsWatched())
1117 			return err;
1118 	} else
1119 		clone = *message;
1120 
1121 	clone.AddInt64("when", (int64)system_time());
1122 	clone.AddPointer("source", this);
1123 	clone.AddMessenger("be:sender", BMessenger(this));
1124 
1125 	if (fListType == B_SINGLE_SELECTION_LIST)
1126 		clone.AddInt32("index", fFirstSelected);
1127 	else {
1128 		if (fFirstSelected >= 0) {
1129 			for (int32 i = fFirstSelected; i <= fLastSelected; i++) {
1130 				if (ItemAt(i)->IsSelected())
1131 					clone.AddInt32("index", i);
1132 			}
1133 		}
1134 	}
1135 
1136 	if (message)
1137 		err = BInvoker::Invoke(&clone);
1138 
1139 	SendNotices(kind, &clone);
1140 
1141 	return err;
1142 }
1143 
1144 
1145 void
1146 BListView::DeselectAll()
1147 {
1148 	if (_DeselectAll(-1, -1)) {
1149 		SelectionChanged();
1150 		InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED);
1151 	}
1152 }
1153 
1154 
1155 void
1156 BListView::DeselectExcept(int32 exceptFrom, int32 exceptTo)
1157 {
1158 	if (exceptFrom > exceptTo || exceptFrom < 0 || exceptTo < 0)
1159 		return;
1160 
1161 	if (_DeselectAll(exceptFrom, exceptTo)) {
1162 		SelectionChanged();
1163 		InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED);
1164 	}
1165 }
1166 
1167 
1168 void
1169 BListView::Deselect(int32 index)
1170 {
1171 	if (_Deselect(index)) {
1172 		SelectionChanged();
1173 		InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED);
1174 	}
1175 }
1176 
1177 
1178 void
1179 BListView::SelectionChanged()
1180 {
1181 	// Hook method to be implemented by subclasses
1182 }
1183 
1184 
1185 void
1186 BListView::SortItems(int (*cmp)(const void *, const void *))
1187 {
1188 	if (_DeselectAll(-1, -1)) {
1189 		SelectionChanged();
1190 		InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED);
1191 	}
1192 
1193 	fList.SortItems(cmp);
1194 	_RecalcItemTops(0);
1195 	Invalidate();
1196 }
1197 
1198 
1199 bool
1200 BListView::SwapItems(int32 a, int32 b)
1201 {
1202 	MiscData data;
1203 
1204 	data.swap.a = a;
1205 	data.swap.b = b;
1206 
1207 	return DoMiscellaneous(B_SWAP_OP, &data);
1208 }
1209 
1210 
1211 bool
1212 BListView::MoveItem(int32 from, int32 to)
1213 {
1214 	MiscData data;
1215 
1216 	data.move.from = from;
1217 	data.move.to = to;
1218 
1219 	return DoMiscellaneous(B_MOVE_OP, &data);
1220 }
1221 
1222 
1223 bool
1224 BListView::ReplaceItem(int32 index, BListItem* item)
1225 {
1226 	MiscData data;
1227 
1228 	data.replace.index = index;
1229 	data.replace.item = item;
1230 
1231 	return DoMiscellaneous(B_REPLACE_OP, &data);
1232 }
1233 
1234 
1235 BRect
1236 BListView::ItemFrame(int32 index)
1237 {
1238 	BRect frame = Bounds();
1239 	if (index < 0 || index >= CountItems()) {
1240 		frame.top = 0;
1241 		frame.bottom = -1;
1242 	} else {
1243 		BListItem* item = ItemAt(index);
1244 		frame.top = item->Top();
1245 		frame.bottom = item->Bottom();
1246 	}
1247 	return frame;
1248 }
1249 
1250 
1251 // #pragma mark -
1252 
1253 
1254 BHandler*
1255 BListView::ResolveSpecifier(BMessage* message, int32 index,
1256 	BMessage* specifier, int32 form, const char* property)
1257 {
1258 	BPropertyInfo propInfo(sProperties);
1259 
1260 	if (propInfo.FindMatch(message, 0, specifier, form, property) < 0) {
1261 		return BView::ResolveSpecifier(message, index, specifier, form,
1262 			property);
1263 	}
1264 
1265 	// TODO: msg->AddInt32("_match_code_", );
1266 
1267 	return this;
1268 }
1269 
1270 
1271 status_t
1272 BListView::GetSupportedSuites(BMessage* data)
1273 {
1274 	if (data == NULL)
1275 		return B_BAD_VALUE;
1276 
1277 	status_t err = data->AddString("suites", "suite/vnd.Be-list-view");
1278 
1279 	BPropertyInfo propertyInfo(sProperties);
1280 	if (err == B_OK)
1281 		err = data->AddFlat("messages", &propertyInfo);
1282 
1283 	if (err == B_OK)
1284 		return BView::GetSupportedSuites(data);
1285 	return err;
1286 }
1287 
1288 
1289 status_t
1290 BListView::Perform(perform_code code, void* _data)
1291 {
1292 	switch (code) {
1293 		case PERFORM_CODE_MIN_SIZE:
1294 			((perform_data_min_size*)_data)->return_value
1295 				= BListView::MinSize();
1296 			return B_OK;
1297 		case PERFORM_CODE_MAX_SIZE:
1298 			((perform_data_max_size*)_data)->return_value
1299 				= BListView::MaxSize();
1300 			return B_OK;
1301 		case PERFORM_CODE_PREFERRED_SIZE:
1302 			((perform_data_preferred_size*)_data)->return_value
1303 				= BListView::PreferredSize();
1304 			return B_OK;
1305 		case PERFORM_CODE_LAYOUT_ALIGNMENT:
1306 			((perform_data_layout_alignment*)_data)->return_value
1307 				= BListView::LayoutAlignment();
1308 			return B_OK;
1309 		case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH:
1310 			((perform_data_has_height_for_width*)_data)->return_value
1311 				= BListView::HasHeightForWidth();
1312 			return B_OK;
1313 		case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH:
1314 		{
1315 			perform_data_get_height_for_width* data
1316 				= (perform_data_get_height_for_width*)_data;
1317 			BListView::GetHeightForWidth(data->width, &data->min, &data->max,
1318 				&data->preferred);
1319 			return B_OK;
1320 		}
1321 		case PERFORM_CODE_SET_LAYOUT:
1322 		{
1323 			perform_data_set_layout* data = (perform_data_set_layout*)_data;
1324 			BListView::SetLayout(data->layout);
1325 			return B_OK;
1326 		}
1327 		case PERFORM_CODE_LAYOUT_INVALIDATED:
1328 		{
1329 			perform_data_layout_invalidated* data
1330 				= (perform_data_layout_invalidated*)_data;
1331 			BListView::LayoutInvalidated(data->descendants);
1332 			return B_OK;
1333 		}
1334 		case PERFORM_CODE_DO_LAYOUT:
1335 		{
1336 			BListView::DoLayout();
1337 			return B_OK;
1338 		}
1339 	}
1340 
1341 	return BView::Perform(code, _data);
1342 }
1343 
1344 
1345 bool
1346 BListView::DoMiscellaneous(MiscCode code, MiscData* data)
1347 {
1348 	if (code > B_SWAP_OP)
1349 		return false;
1350 
1351 	switch (code) {
1352 		case B_NO_OP:
1353 			break;
1354 
1355 		case B_REPLACE_OP:
1356 			return _ReplaceItem(data->replace.index, data->replace.item);
1357 
1358 		case B_MOVE_OP:
1359 			return _MoveItem(data->move.from, data->move.to);
1360 
1361 		case B_SWAP_OP:
1362 			return _SwapItems(data->swap.a, data->swap.b);
1363 	}
1364 
1365 	return false;
1366 }
1367 
1368 
1369 // #pragma mark -
1370 
1371 
1372 void BListView::_ReservedListView2() {}
1373 void BListView::_ReservedListView3() {}
1374 void BListView::_ReservedListView4() {}
1375 
1376 
1377 BListView&
1378 BListView::operator=(const BListView& /*other*/)
1379 {
1380 	return *this;
1381 }
1382 
1383 
1384 // #pragma mark -
1385 
1386 
1387 void
1388 BListView::_InitObject(list_view_type type)
1389 {
1390 	fListType = type;
1391 	fFirstSelected = -1;
1392 	fLastSelected = -1;
1393 	fAnchorIndex = -1;
1394 	fSelectMessage = NULL;
1395 	fScrollView = NULL;
1396 	fTrack = new track_data;
1397 	fTrack->try_drag = false;
1398 	fTrack->item_index = -1;
1399 
1400 	SetViewColor(ui_color(B_LIST_BACKGROUND_COLOR));
1401 	SetLowColor(ui_color(B_LIST_BACKGROUND_COLOR));
1402 }
1403 
1404 
1405 void
1406 BListView::_FixupScrollBar()
1407 {
1408 	BScrollBar* vertScroller = ScrollBar(B_VERTICAL);
1409 	if (!vertScroller)
1410 		return;
1411 
1412 	BRect bounds = Bounds();
1413 	int32 count = CountItems();
1414 
1415 	float itemHeight = 0.0;
1416 
1417 	if (CountItems() > 0)
1418 		itemHeight = ItemAt(CountItems() - 1)->Bottom();
1419 
1420 	if (bounds.Height() > itemHeight) {
1421 		// no scrolling
1422 		vertScroller->SetRange(0.0, 0.0);
1423 		vertScroller->SetValue(0.0);
1424 			// also scrolls ListView to the top
1425 	} else {
1426 		vertScroller->SetRange(0.0, itemHeight - bounds.Height() - 1.0);
1427 		vertScroller->SetProportion(bounds.Height () / itemHeight);
1428 		// scroll up if there is empty room on bottom
1429 		if (itemHeight < bounds.bottom)
1430 			ScrollBy(0.0, bounds.bottom - itemHeight);
1431 	}
1432 
1433 	if (count != 0)
1434 		vertScroller->SetSteps(ceilf(FirstItem()->Height()), bounds.Height());
1435 }
1436 
1437 
1438 void
1439 BListView::_InvalidateFrom(int32 index)
1440 {
1441 	// make sure index is behind last valid index
1442 	int32 count = CountItems();
1443 	if (index >= count)
1444 		index = count;
1445 
1446 	// take the item before the wanted one,
1447 	// because that might already be removed
1448 	index--;
1449 	BRect dirty = Bounds();
1450 	if (index >= 0)
1451 		dirty.top = ItemFrame(index).bottom + 1;
1452 
1453 	Invalidate(dirty);
1454 }
1455 
1456 
1457 void
1458 BListView::_FontChanged()
1459 {
1460 	BFont font;
1461 	GetFont(&font);
1462 	for (int32 i = 0; i < CountItems(); i++) {
1463 		ItemAt(i)->SetTop((i > 0) ? ItemAt(i - 1)->Bottom() + 1.0 : 0.0);
1464 		ItemAt(i)->Update(this, &font);
1465 	}
1466 }
1467 
1468 
1469 /*!	Selects the item at the specified \a index, and returns \c true in
1470 	case the selection was changed because of this method.
1471 	If \a extend is \c false, all previously selected items are deselected.
1472 */
1473 bool
1474 BListView::_Select(int32 index, bool extend)
1475 {
1476 	if (index < 0 || index >= CountItems())
1477 		return false;
1478 
1479 	// only lock the window when there is one
1480 	BAutolock locker(Window());
1481 	if (Window() != NULL && !locker.IsLocked())
1482 		return false;
1483 
1484 	bool changed = false;
1485 
1486 	if (!extend && fFirstSelected != -1)
1487 		changed = _DeselectAll(index, index);
1488 
1489 	fAnchorIndex = index;
1490 
1491 	BListItem* item = ItemAt(index);
1492 	if (!item->IsEnabled() || item->IsSelected()) {
1493 		// if the item is already selected, or can't be selected,
1494 		// we're done here
1495 		return changed;
1496 	}
1497 
1498 	// keep track of first and last selected item
1499 	if (fFirstSelected == -1) {
1500 		// no previous selection
1501 		fFirstSelected = index;
1502 		fLastSelected = index;
1503 	} else if (index < fFirstSelected) {
1504 		fFirstSelected = index;
1505 	} else if (index > fLastSelected) {
1506 		fLastSelected = index;
1507 	}
1508 
1509 	ItemAt(index)->Select();
1510 	if (Window())
1511 		InvalidateItem(index);
1512 
1513 	return true;
1514 }
1515 
1516 
1517 /*!
1518 	Selects the items between \a from and \a to, and returns \c true in
1519 	case the selection was changed because of this method.
1520 	If \a extend is \c false, all previously selected items are deselected.
1521 */
1522 bool
1523 BListView::_Select(int32 from, int32 to, bool extend)
1524 {
1525 	if (to < from)
1526 		return false;
1527 
1528 	BAutolock locker(Window());
1529 	if (Window() && !locker.IsLocked())
1530 		return false;
1531 
1532 	bool changed = false;
1533 
1534 	if (fFirstSelected != -1 && !extend)
1535 		changed = _DeselectAll(from, to);
1536 
1537 	if (fFirstSelected == -1) {
1538 		fFirstSelected = from;
1539 		fLastSelected = to;
1540 	} else {
1541 		if (from < fFirstSelected)
1542 			fFirstSelected = from;
1543 		if (to > fLastSelected)
1544 			fLastSelected = to;
1545 	}
1546 
1547 	for (int32 i = from; i <= to; ++i) {
1548 		BListItem *item = ItemAt(i);
1549 		if (item && !item->IsSelected()) {
1550 			item->Select();
1551 			if (Window())
1552 				InvalidateItem(i);
1553 			changed = true;
1554 		}
1555 	}
1556 
1557 	return changed;
1558 }
1559 
1560 
1561 bool
1562 BListView::_Deselect(int32 index)
1563 {
1564 	if (index < 0 || index >= CountItems())
1565 		return false;
1566 
1567 	BWindow *window = Window();
1568 	BAutolock locker(window);
1569 	if (window && !locker.IsLocked())
1570 		return false;
1571 
1572 	BListItem *item = ItemAt(index);
1573 
1574 	if (item && item->IsSelected()) {
1575 		BRect frame(ItemFrame(index));
1576 		BRect bounds(Bounds());
1577 
1578 		item->Deselect();
1579 
1580 		if (fFirstSelected == index && fLastSelected == index) {
1581 			fFirstSelected = -1;
1582 			fLastSelected = -1;
1583 		} else {
1584 			if (fFirstSelected == index)
1585 				fFirstSelected = _CalcFirstSelected(index);
1586 
1587 			if (fLastSelected == index)
1588 				fLastSelected = _CalcLastSelected(index);
1589 		}
1590 
1591 		if (window && bounds.Intersects(frame))
1592 			DrawItem(ItemAt(index), frame, true);
1593 	}
1594 
1595 	return true;
1596 }
1597 
1598 
1599 bool
1600 BListView::_DeselectAll(int32 exceptFrom, int32 exceptTo)
1601 {
1602 	if (fFirstSelected == -1)
1603 		return false;
1604 
1605 	BAutolock locker(Window());
1606 	if (Window() && !locker.IsLocked())
1607 		return false;
1608 
1609 	bool changed = false;
1610 
1611 	for (int32 index = fFirstSelected; index <= fLastSelected; index++) {
1612 		// don't deselect the items we shouldn't deselect
1613 		if (exceptFrom != -1 && exceptFrom <= index && exceptTo >= index)
1614 			continue;
1615 
1616 		BListItem *item = ItemAt(index);
1617 		if (item && item->IsSelected()) {
1618 			item->Deselect();
1619 			InvalidateItem(index);
1620 			changed = true;
1621 		}
1622 	}
1623 
1624 	if (!changed)
1625 		return false;
1626 
1627 	if (exceptFrom != -1) {
1628 		fFirstSelected = _CalcFirstSelected(exceptFrom);
1629 		fLastSelected = _CalcLastSelected(exceptTo);
1630 	} else
1631 		fFirstSelected = fLastSelected = -1;
1632 
1633 	return true;
1634 }
1635 
1636 
1637 int32
1638 BListView::_CalcFirstSelected(int32 after)
1639 {
1640 	if (after >= CountItems())
1641 		return -1;
1642 
1643 	int32 count = CountItems();
1644 	for (int32 i = after; i < count; i++) {
1645 		if (ItemAt(i)->IsSelected())
1646 			return i;
1647 	}
1648 
1649 	return -1;
1650 }
1651 
1652 
1653 int32
1654 BListView::_CalcLastSelected(int32 before)
1655 {
1656 	if (before < 0)
1657 		return -1;
1658 
1659 	before = min_c(CountItems() - 1, before);
1660 
1661 	for (int32 i = before; i >= 0; i--) {
1662 		if (ItemAt(i)->IsSelected())
1663 			return i;
1664 	}
1665 
1666 	return -1;
1667 }
1668 
1669 
1670 void
1671 BListView::DrawItem(BListItem* item, BRect itemRect, bool complete)
1672 {
1673 	item->DrawItem(this, itemRect, complete);
1674 }
1675 
1676 
1677 bool
1678 BListView::_SwapItems(int32 a, int32 b)
1679 {
1680 	// remember frames of items before anyhing happens,
1681 	// the tricky situation is when the two items have
1682 	// a different height
1683 	BRect aFrame = ItemFrame(a);
1684 	BRect bFrame = ItemFrame(b);
1685 
1686 	if (!fList.SwapItems(a, b))
1687 		return false;
1688 
1689 	if (a == b) {
1690 		// nothing to do, but success nevertheless
1691 		return true;
1692 	}
1693 
1694 	// track anchor item
1695 	if (fAnchorIndex == a)
1696 		fAnchorIndex = b;
1697 	else if (fAnchorIndex == b)
1698 		fAnchorIndex = a;
1699 
1700 	// track selection
1701 	// NOTE: this is only important if the selection status
1702 	// of both items is not the same
1703 	int32 first = min_c(a, b);
1704 	int32 last = max_c(a, b);
1705 	if (ItemAt(a)->IsSelected() != ItemAt(b)->IsSelected()) {
1706 		if (first < fFirstSelected || last > fLastSelected) {
1707 			_RescanSelection(min_c(first, fFirstSelected),
1708 				max_c(last, fLastSelected));
1709 		}
1710 		// though the actually selected items stayed the
1711 		// same, the selection has still changed
1712 		SelectionChanged();
1713 	}
1714 
1715 	ItemAt(a)->SetTop(aFrame.top);
1716 	ItemAt(b)->SetTop(bFrame.top);
1717 
1718 	// take care of invalidation
1719 	if (Window()) {
1720 		// NOTE: window looper is assumed to be locked!
1721 		if (aFrame.Height() != bFrame.Height()) {
1722 			_RecalcItemTops(first, last);
1723 			// items in between shifted visually
1724 			Invalidate(aFrame | bFrame);
1725 		} else {
1726 			Invalidate(aFrame);
1727 			Invalidate(bFrame);
1728 		}
1729 	}
1730 
1731 	return true;
1732 }
1733 
1734 
1735 bool
1736 BListView::_MoveItem(int32 from, int32 to)
1737 {
1738 	// remember item frames before doing anything
1739 	BRect frameFrom = ItemFrame(from);
1740 	BRect frameTo = ItemFrame(to);
1741 
1742 	if (!fList.MoveItem(from, to))
1743 		return false;
1744 
1745 	// track anchor item
1746 	if (fAnchorIndex == from)
1747 		fAnchorIndex = to;
1748 
1749 	// track selection
1750 	if (ItemAt(to)->IsSelected()) {
1751 		_RescanSelection(from, to);
1752 		// though the actually selected items stayed the
1753 		// same, the selection has still changed
1754 		SelectionChanged();
1755 	}
1756 
1757 	_RecalcItemTops((to > from) ? from : to);
1758 
1759 	// take care of invalidation
1760 	if (Window()) {
1761 		// NOTE: window looper is assumed to be locked!
1762 		Invalidate(frameFrom | frameTo);
1763 	}
1764 
1765 	return true;
1766 }
1767 
1768 
1769 bool
1770 BListView::_ReplaceItem(int32 index, BListItem* item)
1771 {
1772 	if (item == NULL)
1773 		return false;
1774 
1775 	BListItem* old = ItemAt(index);
1776 	if (!old)
1777 		return false;
1778 
1779 	BRect frame = ItemFrame(index);
1780 
1781 	bool selectionChanged = old->IsSelected() != item->IsSelected();
1782 
1783 	// replace item
1784 	if (!fList.ReplaceItem(index, item))
1785 		return false;
1786 
1787 	// tack selection
1788 	if (selectionChanged) {
1789 		int32 start = min_c(fFirstSelected, index);
1790 		int32 end = max_c(fLastSelected, index);
1791 		_RescanSelection(start, end);
1792 		SelectionChanged();
1793 	}
1794 	_RecalcItemTops(index);
1795 
1796 	bool itemHeightChanged = frame != ItemFrame(index);
1797 
1798 	// take care of invalidation
1799 	if (Window()) {
1800 		// NOTE: window looper is assumed to be locked!
1801 		if (itemHeightChanged)
1802 			_InvalidateFrom(index);
1803 		else
1804 			Invalidate(frame);
1805 	}
1806 
1807 	if (itemHeightChanged)
1808 		_FixupScrollBar();
1809 
1810 	return true;
1811 }
1812 
1813 
1814 void
1815 BListView::_RescanSelection(int32 from, int32 to)
1816 {
1817 	if (from > to) {
1818 		int32 tmp = from;
1819 		from = to;
1820 		to = tmp;
1821 	}
1822 
1823 	from = max_c(0, from);
1824 	to = min_c(to, CountItems() - 1);
1825 
1826 	if (fAnchorIndex != -1) {
1827 		if (fAnchorIndex == from)
1828 			fAnchorIndex = to;
1829 		else if (fAnchorIndex == to)
1830 			fAnchorIndex = from;
1831 	}
1832 
1833 	for (int32 i = from; i <= to; i++) {
1834 		if (ItemAt(i)->IsSelected()) {
1835 			fFirstSelected = i;
1836 			break;
1837 		}
1838 	}
1839 
1840 	if (fFirstSelected > from)
1841 		from = fFirstSelected;
1842 
1843 	fLastSelected = fFirstSelected;
1844 	for (int32 i = from; i <= to; i++) {
1845 		if (ItemAt(i)->IsSelected())
1846 			fLastSelected = i;
1847 	}
1848 }
1849 
1850 
1851 void
1852 BListView::_RecalcItemTops(int32 start, int32 end)
1853 {
1854 	int32 count = CountItems();
1855 	if ((start < 0) || (start >= count))
1856 		return;
1857 
1858 	if (end >= 0)
1859 		count = end + 1;
1860 
1861 	float top = (start == 0) ? 0.0 : ItemAt(start - 1)->Bottom() + 1.0;
1862 
1863 	for (int32 i = start; i < count; i++) {
1864 		BListItem *item = ItemAt(i);
1865 		item->SetTop(top);
1866 		top += ceilf(item->Height());
1867 	}
1868 }
1869