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