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