xref: /haiku/src/kits/interface/ListView.cpp (revision 2cad94c1c30b6223ad8c08710b26e071d32e9979)
1 /*
2  * Copyright 2001-2015 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* data, bool deep) const
164 {
165 	status_t status = BView::Archive(data, deep);
166 	if (status < B_OK)
167 		return status;
168 
169 	status = data->AddInt32("_lv_type", fListType);
170 	if (status == B_OK && deep) {
171 		BListItem* item;
172 		int32 i = 0;
173 
174 		while ((item = ItemAt(i++)) != NULL) {
175 			BMessage subData;
176 			status = item->Archive(&subData, true);
177 			if (status >= B_OK)
178 				status = data->AddMessage("_l_items", &subData);
179 
180 			if (status < B_OK)
181 				break;
182 		}
183 	}
184 
185 	if (status >= B_OK && InvocationMessage() != NULL)
186 		status = data->AddMessage("_msg", InvocationMessage());
187 
188 	if (status == B_OK && fSelectMessage != NULL)
189 		status = data->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 newWidth, float newHeight)
254 {
255 	_FixupScrollBar();
256 }
257 
258 
259 void
260 BListView::FrameMoved(BPoint newPosition)
261 {
262 	BView::FrameMoved(newPosition);
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 active)
278 {
279 	BView::WindowActivated(active);
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 what, const char* property)
1310 {
1311 	BPropertyInfo propInfo(sProperties);
1312 
1313 	if (propInfo.FindMatch(message, 0, specifier, what, property) < 0) {
1314 		return BView::ResolveSpecifier(message, index, specifier, what,
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 	SetViewUIColor(B_LIST_BACKGROUND_COLOR);
1453 	SetLowUIColor(B_LIST_BACKGROUND_COLOR);
1454 }
1455 
1456 
1457 void
1458 BListView::_FixupScrollBar()
1459 {
1460 	BScrollBar* vertScroller = ScrollBar(B_VERTICAL);
1461 	if (!vertScroller)
1462 		return;
1463 
1464 	BRect bounds = Bounds();
1465 	int32 count = CountItems();
1466 
1467 	float itemHeight = 0.0;
1468 
1469 	if (CountItems() > 0)
1470 		itemHeight = ItemAt(CountItems() - 1)->Bottom();
1471 
1472 	if (bounds.Height() > itemHeight) {
1473 		// no scrolling
1474 		vertScroller->SetRange(0.0, 0.0);
1475 		vertScroller->SetValue(0.0);
1476 			// also scrolls ListView to the top
1477 	} else {
1478 		vertScroller->SetRange(0.0, itemHeight - bounds.Height() - 1.0);
1479 		vertScroller->SetProportion(bounds.Height () / itemHeight);
1480 		// scroll up if there is empty room on bottom
1481 		if (itemHeight < bounds.bottom)
1482 			ScrollBy(0.0, bounds.bottom - itemHeight);
1483 	}
1484 
1485 	if (count != 0)
1486 		vertScroller->SetSteps(ceilf(FirstItem()->Height()), bounds.Height());
1487 }
1488 
1489 
1490 void
1491 BListView::_InvalidateFrom(int32 index)
1492 {
1493 	// make sure index is behind last valid index
1494 	int32 count = CountItems();
1495 	if (index >= count)
1496 		index = count;
1497 
1498 	// take the item before the wanted one,
1499 	// because that might already be removed
1500 	index--;
1501 	BRect dirty = Bounds();
1502 	if (index >= 0)
1503 		dirty.top = ItemFrame(index).bottom + 1;
1504 
1505 	Invalidate(dirty);
1506 }
1507 
1508 
1509 void
1510 BListView::_FontChanged()
1511 {
1512 	BFont font;
1513 	GetFont(&font);
1514 	for (int32 i = 0; i < CountItems(); i++) {
1515 		ItemAt(i)->SetTop((i > 0) ? ItemAt(i - 1)->Bottom() + 1.0 : 0.0);
1516 		ItemAt(i)->Update(this, &font);
1517 	}
1518 }
1519 
1520 
1521 /*!	Selects the item at the specified \a index, 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 index, bool extend)
1527 {
1528 	if (index < 0 || index >= CountItems())
1529 		return false;
1530 
1531 	// only lock the window when there is one
1532 	BAutolock locker(Window());
1533 	if (Window() != NULL && !locker.IsLocked())
1534 		return false;
1535 
1536 	bool changed = false;
1537 
1538 	if (!extend && fFirstSelected != -1)
1539 		changed = _DeselectAll(index, index);
1540 
1541 	fAnchorIndex = index;
1542 
1543 	BListItem* item = ItemAt(index);
1544 	if (!item->IsEnabled() || item->IsSelected()) {
1545 		// if the item is already selected, or can't be selected,
1546 		// we're done here
1547 		return changed;
1548 	}
1549 
1550 	// keep track of first and last selected item
1551 	if (fFirstSelected == -1) {
1552 		// no previous selection
1553 		fFirstSelected = index;
1554 		fLastSelected = index;
1555 	} else if (index < fFirstSelected) {
1556 		fFirstSelected = index;
1557 	} else if (index > fLastSelected) {
1558 		fLastSelected = index;
1559 	}
1560 
1561 	item->Select();
1562 	if (Window() != NULL)
1563 		InvalidateItem(index);
1564 
1565 	return true;
1566 }
1567 
1568 
1569 /*!
1570 	Selects the items between \a from and \a to, and returns \c true in
1571 	case the selection was changed because of this method.
1572 	If \a extend is \c false, all previously selected items are deselected.
1573 */
1574 bool
1575 BListView::_Select(int32 from, int32 to, bool extend)
1576 {
1577 	if (to < from)
1578 		return false;
1579 
1580 	BAutolock locker(Window());
1581 	if (Window() && !locker.IsLocked())
1582 		return false;
1583 
1584 	bool changed = false;
1585 
1586 	if (fFirstSelected != -1 && !extend)
1587 		changed = _DeselectAll(from, to);
1588 
1589 	if (fFirstSelected == -1) {
1590 		fFirstSelected = from;
1591 		fLastSelected = to;
1592 	} else {
1593 		if (from < fFirstSelected)
1594 			fFirstSelected = from;
1595 		if (to > fLastSelected)
1596 			fLastSelected = to;
1597 	}
1598 
1599 	for (int32 i = from; i <= to; ++i) {
1600 		BListItem* item = ItemAt(i);
1601 		if (item != NULL && !item->IsSelected() && item->IsEnabled()) {
1602 			item->Select();
1603 			if (Window() != NULL)
1604 				InvalidateItem(i);
1605 			changed = true;
1606 		}
1607 	}
1608 
1609 	return changed;
1610 }
1611 
1612 
1613 bool
1614 BListView::_Deselect(int32 index)
1615 {
1616 	if (index < 0 || index >= CountItems())
1617 		return false;
1618 
1619 	BWindow* window = Window();
1620 	BAutolock locker(window);
1621 	if (window != NULL && !locker.IsLocked())
1622 		return false;
1623 
1624 	BListItem* item = ItemAt(index);
1625 
1626 	if (item != NULL && item->IsSelected()) {
1627 		BRect frame(ItemFrame(index));
1628 		BRect bounds(Bounds());
1629 
1630 		item->Deselect();
1631 
1632 		if (fFirstSelected == index && fLastSelected == index) {
1633 			fFirstSelected = -1;
1634 			fLastSelected = -1;
1635 		} else {
1636 			if (fFirstSelected == index)
1637 				fFirstSelected = _CalcFirstSelected(index);
1638 
1639 			if (fLastSelected == index)
1640 				fLastSelected = _CalcLastSelected(index);
1641 		}
1642 
1643 		if (window && bounds.Intersects(frame))
1644 			DrawItem(ItemAt(index), frame, true);
1645 	}
1646 
1647 	return true;
1648 }
1649 
1650 
1651 bool
1652 BListView::_DeselectAll(int32 exceptFrom, int32 exceptTo)
1653 {
1654 	if (fFirstSelected == -1)
1655 		return false;
1656 
1657 	BAutolock locker(Window());
1658 	if (Window() && !locker.IsLocked())
1659 		return false;
1660 
1661 	bool changed = false;
1662 
1663 	for (int32 index = fFirstSelected; index <= fLastSelected; index++) {
1664 		// don't deselect the items we shouldn't deselect
1665 		if (exceptFrom != -1 && exceptFrom <= index && exceptTo >= index)
1666 			continue;
1667 
1668 		BListItem* item = ItemAt(index);
1669 		if (item != NULL && item->IsSelected()) {
1670 			item->Deselect();
1671 			InvalidateItem(index);
1672 			changed = true;
1673 		}
1674 	}
1675 
1676 	if (!changed)
1677 		return false;
1678 
1679 	if (exceptFrom != -1) {
1680 		fFirstSelected = _CalcFirstSelected(exceptFrom);
1681 		fLastSelected = _CalcLastSelected(exceptTo);
1682 	} else
1683 		fFirstSelected = fLastSelected = -1;
1684 
1685 	return true;
1686 }
1687 
1688 
1689 int32
1690 BListView::_CalcFirstSelected(int32 after)
1691 {
1692 	if (after >= CountItems())
1693 		return -1;
1694 
1695 	int32 count = CountItems();
1696 	for (int32 i = after; i < count; i++) {
1697 		if (ItemAt(i)->IsSelected())
1698 			return i;
1699 	}
1700 
1701 	return -1;
1702 }
1703 
1704 
1705 int32
1706 BListView::_CalcLastSelected(int32 before)
1707 {
1708 	if (before < 0)
1709 		return -1;
1710 
1711 	before = min_c(CountItems() - 1, before);
1712 
1713 	for (int32 i = before; i >= 0; i--) {
1714 		if (ItemAt(i)->IsSelected())
1715 			return i;
1716 	}
1717 
1718 	return -1;
1719 }
1720 
1721 
1722 void
1723 BListView::DrawItem(BListItem* item, BRect itemRect, bool complete)
1724 {
1725 	if (!item->IsEnabled()) {
1726 		rgb_color textColor = ui_color(B_LIST_ITEM_TEXT_COLOR);
1727 		rgb_color disabledColor;
1728 		if (textColor.red + textColor.green + textColor.blue > 128 * 3)
1729 			disabledColor = tint_color(textColor, B_DARKEN_2_TINT);
1730 		else
1731 			disabledColor = tint_color(textColor, B_LIGHTEN_2_TINT);
1732 
1733 		SetHighColor(disabledColor);
1734 	} else if (item->IsSelected())
1735 		SetHighColor(ui_color(B_LIST_SELECTED_ITEM_TEXT_COLOR));
1736 	else
1737 		SetHighColor(ui_color(B_LIST_ITEM_TEXT_COLOR));
1738 
1739 	item->DrawItem(this, itemRect, complete);
1740 }
1741 
1742 
1743 bool
1744 BListView::_SwapItems(int32 a, int32 b)
1745 {
1746 	// remember frames of items before anything happens,
1747 	// the tricky situation is when the two items have
1748 	// a different height
1749 	BRect aFrame = ItemFrame(a);
1750 	BRect bFrame = ItemFrame(b);
1751 
1752 	if (!fList.SwapItems(a, b))
1753 		return false;
1754 
1755 	if (a == b) {
1756 		// nothing to do, but success nevertheless
1757 		return true;
1758 	}
1759 
1760 	// track anchor item
1761 	if (fAnchorIndex == a)
1762 		fAnchorIndex = b;
1763 	else if (fAnchorIndex == b)
1764 		fAnchorIndex = a;
1765 
1766 	// track selection
1767 	// NOTE: this is only important if the selection status
1768 	// of both items is not the same
1769 	int32 first = min_c(a, b);
1770 	int32 last = max_c(a, b);
1771 	if (ItemAt(a)->IsSelected() != ItemAt(b)->IsSelected()) {
1772 		if (first < fFirstSelected || last > fLastSelected) {
1773 			_RescanSelection(min_c(first, fFirstSelected),
1774 				max_c(last, fLastSelected));
1775 		}
1776 		// though the actually selected items stayed the
1777 		// same, the selection has still changed
1778 		SelectionChanged();
1779 	}
1780 
1781 	ItemAt(a)->SetTop(aFrame.top);
1782 	ItemAt(b)->SetTop(bFrame.top);
1783 
1784 	// take care of invalidation
1785 	if (Window()) {
1786 		// NOTE: window looper is assumed to be locked!
1787 		if (aFrame.Height() != bFrame.Height()) {
1788 			_RecalcItemTops(first, last);
1789 			// items in between shifted visually
1790 			Invalidate(aFrame | bFrame);
1791 		} else {
1792 			Invalidate(aFrame);
1793 			Invalidate(bFrame);
1794 		}
1795 	}
1796 
1797 	return true;
1798 }
1799 
1800 
1801 bool
1802 BListView::_MoveItem(int32 from, int32 to)
1803 {
1804 	// remember item frames before doing anything
1805 	BRect frameFrom = ItemFrame(from);
1806 	BRect frameTo = ItemFrame(to);
1807 
1808 	if (!fList.MoveItem(from, to))
1809 		return false;
1810 
1811 	// track anchor item
1812 	if (fAnchorIndex == from)
1813 		fAnchorIndex = to;
1814 
1815 	// track selection
1816 	if (ItemAt(to)->IsSelected()) {
1817 		_RescanSelection(from, to);
1818 		// though the actually selected items stayed the
1819 		// same, the selection has still changed
1820 		SelectionChanged();
1821 	}
1822 
1823 	_RecalcItemTops((to > from) ? from : to);
1824 
1825 	// take care of invalidation
1826 	if (Window()) {
1827 		// NOTE: window looper is assumed to be locked!
1828 		Invalidate(frameFrom | frameTo);
1829 	}
1830 
1831 	return true;
1832 }
1833 
1834 
1835 bool
1836 BListView::_ReplaceItem(int32 index, BListItem* item)
1837 {
1838 	if (item == NULL)
1839 		return false;
1840 
1841 	BListItem* old = ItemAt(index);
1842 	if (!old)
1843 		return false;
1844 
1845 	BRect frame = ItemFrame(index);
1846 
1847 	bool selectionChanged = old->IsSelected() != item->IsSelected();
1848 
1849 	// replace item
1850 	if (!fList.ReplaceItem(index, item))
1851 		return false;
1852 
1853 	// tack selection
1854 	if (selectionChanged) {
1855 		int32 start = min_c(fFirstSelected, index);
1856 		int32 end = max_c(fLastSelected, index);
1857 		_RescanSelection(start, end);
1858 		SelectionChanged();
1859 	}
1860 	_RecalcItemTops(index);
1861 
1862 	bool itemHeightChanged = frame != ItemFrame(index);
1863 
1864 	// take care of invalidation
1865 	if (Window()) {
1866 		// NOTE: window looper is assumed to be locked!
1867 		if (itemHeightChanged)
1868 			_InvalidateFrom(index);
1869 		else
1870 			Invalidate(frame);
1871 	}
1872 
1873 	if (itemHeightChanged)
1874 		_FixupScrollBar();
1875 
1876 	return true;
1877 }
1878 
1879 
1880 void
1881 BListView::_RescanSelection(int32 from, int32 to)
1882 {
1883 	if (from > to) {
1884 		int32 tmp = from;
1885 		from = to;
1886 		to = tmp;
1887 	}
1888 
1889 	from = max_c(0, from);
1890 	to = min_c(to, CountItems() - 1);
1891 
1892 	if (fAnchorIndex != -1) {
1893 		if (fAnchorIndex == from)
1894 			fAnchorIndex = to;
1895 		else if (fAnchorIndex == to)
1896 			fAnchorIndex = from;
1897 	}
1898 
1899 	for (int32 i = from; i <= to; i++) {
1900 		if (ItemAt(i)->IsSelected()) {
1901 			fFirstSelected = i;
1902 			break;
1903 		}
1904 	}
1905 
1906 	if (fFirstSelected > from)
1907 		from = fFirstSelected;
1908 
1909 	fLastSelected = fFirstSelected;
1910 	for (int32 i = from; i <= to; i++) {
1911 		if (ItemAt(i)->IsSelected())
1912 			fLastSelected = i;
1913 	}
1914 }
1915 
1916 
1917 void
1918 BListView::_RecalcItemTops(int32 start, int32 end)
1919 {
1920 	int32 count = CountItems();
1921 	if ((start < 0) || (start >= count))
1922 		return;
1923 
1924 	if (end >= 0)
1925 		count = end + 1;
1926 
1927 	float top = (start == 0) ? 0.0 : ItemAt(start - 1)->Bottom() + 1.0;
1928 
1929 	for (int32 i = start; i < count; i++) {
1930 		BListItem *item = ItemAt(i);
1931 		item->SetTop(top);
1932 		top += ceilf(item->Height());
1933 	}
1934 }
1935