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