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