xref: /haiku/src/kits/interface/ListView.cpp (revision eea5774f46bba925156498abf9cb1a1165647bf7)
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 	// drag is over
692 	fTrack->buttons = 0;
693 	fTrack->try_drag = false;
694 	fTrack->is_dragging = false;
695 
696 	// selection updating on drag is for single selection lists only
697 	if (fListType == B_MULTIPLE_SELECTION_LIST)
698 		return BView::MouseUp(where);
699 
700 	int32 index = IndexOf(where);
701 
702 	// bail out if selection hasn't changed
703 	if (index == fTrack->item_index)
704 		return BView::MouseUp(where);
705 
706 	// if mouse up selection is invalid reselect mouse down selection
707 	if (index == -1)
708 		index = fTrack->item_index;
709 
710 	// bail out if mouse down selection invalid
711 	if (index == -1)
712 		return BView::MouseUp(where);
713 
714 	// undo fake selection and select item
715 	BListItem* item = ItemAt(index);
716 	if (item != NULL) {
717 		item->Deselect();
718 		_DoSelection(index);
719 	}
720 
721 	BView::MouseUp(where);
722 }
723 
724 
725 void
726 BListView::MouseMoved(BPoint where, uint32 code, const BMessage* dragMessage)
727 {
728 	if (fTrack->item_index >= 0 && fTrack->try_drag) {
729 		// initiate a drag if the mouse was moved far enough
730 		BPoint offset = where - fTrack->drag_start;
731 		float dragDistance = sqrtf(offset.x * offset.x + offset.y * offset.y);
732 		if (dragDistance >= 5.0f) {
733 			fTrack->try_drag = false;
734 			fTrack->is_dragging = InitiateDrag(fTrack->drag_start,
735 				fTrack->item_index, fTrack->was_selected);
736 		}
737 	}
738 
739 	int32 index = IndexOf(where);
740 	if (index == -1) {
741 		// If where is above top, scroll to the first item,
742 		// else if where is below bottom scroll to the last item.
743 		if (where.y < Bounds().top)
744 			index = 0;
745 		else if (where.y > Bounds().bottom)
746 			index = CountItems() - 1;
747 	}
748 
749 	// don't scroll if button not pressed or index is invalid
750 	int32 lastIndex = fFirstSelected;
751 	if (fTrack->buttons == 0 || index == -1)
752 		return BView::MouseMoved(where, code, dragMessage);
753 
754 	// don't scroll if mouse is left or right of the view
755 	if (where.x < Bounds().left || where.x > Bounds().right)
756 		return BView::MouseMoved(where, code, dragMessage);
757 
758 	// scroll to item under mouse while button is pressed
759 	ScrollTo(index);
760 
761 	if (!fTrack->is_dragging && fListType != B_MULTIPLE_SELECTION_LIST
762 		&& lastIndex != -1 && index != lastIndex) {
763 		// mouse moved over unselected item, fake selection until mouse up
764 		BListItem* last = ItemAt(lastIndex);
765 		BListItem* item = ItemAt(index);
766 		if (last != NULL && item != NULL) {
767 			last->Deselect();
768 			item->Select();
769 
770 			// update selection index
771 			fFirstSelected = fLastSelected = index;
772 
773 			// redraw items whose selection has changed
774 			Invalidate(ItemFrame(lastIndex) | ItemFrame(index));
775 		}
776 	} else
777 		Invalidate();
778 
779 	BView::MouseMoved(where, code, dragMessage);
780 }
781 
782 
783 bool
784 BListView::InitiateDrag(BPoint where, int32 index, bool wasSelected)
785 {
786 	return false;
787 }
788 
789 
790 // #pragma mark -
791 
792 
793 void
794 BListView::ResizeToPreferred()
795 {
796 	BView::ResizeToPreferred();
797 }
798 
799 
800 void
801 BListView::GetPreferredSize(float *_width, float *_height)
802 {
803 	int32 count = CountItems();
804 
805 	if (count > 0) {
806 		float maxWidth = 0.0;
807 		for (int32 i = 0; i < count; i++) {
808 			float itemWidth = ItemAt(i)->Width();
809 			if (itemWidth > maxWidth)
810 				maxWidth = itemWidth;
811 		}
812 
813 		if (_width != NULL)
814 			*_width = maxWidth;
815 		if (_height != NULL)
816 			*_height = ItemAt(count - 1)->Bottom();
817 	} else
818 		BView::GetPreferredSize(_width, _height);
819 }
820 
821 
822 BSize
823 BListView::MinSize()
824 {
825 	// We need a stable min size: the BView implementation uses
826 	// GetPreferredSize(), which by default just returns the current size.
827 	return BLayoutUtils::ComposeSize(ExplicitMinSize(), BSize(10, 10));
828 }
829 
830 
831 BSize
832 BListView::MaxSize()
833 {
834 	return BView::MaxSize();
835 }
836 
837 
838 BSize
839 BListView::PreferredSize()
840 {
841 	// We need a stable preferred size: the BView implementation uses
842 	// GetPreferredSize(), which by default just returns the current size.
843 	return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), BSize(100, 50));
844 }
845 
846 
847 // #pragma mark -
848 
849 
850 void
851 BListView::MakeFocus(bool focused)
852 {
853 	if (IsFocus() == focused)
854 		return;
855 
856 	BView::MakeFocus(focused);
857 
858 	if (fScrollView)
859 		fScrollView->SetBorderHighlighted(focused);
860 }
861 
862 
863 void
864 BListView::SetFont(const BFont* font, uint32 mask)
865 {
866 	BView::SetFont(font, mask);
867 
868 	if (Window() != NULL && !Window()->InViewTransaction())
869 		_UpdateItems();
870 }
871 
872 
873 void
874 BListView::ScrollTo(BPoint point)
875 {
876 	BView::ScrollTo(point);
877 }
878 
879 
880 // #pragma mark - List ops
881 
882 
883 bool
884 BListView::AddItem(BListItem* item, int32 index)
885 {
886 	if (!fList.AddItem(item, index))
887 		return false;
888 
889 	if (fFirstSelected != -1 && index <= fFirstSelected)
890 		fFirstSelected++;
891 
892 	if (fLastSelected != -1 && index <= fLastSelected)
893 		fLastSelected++;
894 
895 	if (Window()) {
896 		BFont font;
897 		GetFont(&font);
898 		item->SetTop((index > 0) ? ItemAt(index - 1)->Bottom() + 1.0 : 0.0);
899 
900 		item->Update(this, &font);
901 		_RecalcItemTops(index + 1);
902 
903 		_FixupScrollBar();
904 		_InvalidateFrom(index);
905 	}
906 
907 	return true;
908 }
909 
910 
911 bool
912 BListView::AddItem(BListItem* item)
913 {
914 	if (!fList.AddItem(item))
915 		return false;
916 
917 	// No need to adapt selection, as this item is the last in the list
918 
919 	if (Window()) {
920 		BFont font;
921 		GetFont(&font);
922 		int32 index = CountItems() - 1;
923 		item->SetTop((index > 0) ? ItemAt(index - 1)->Bottom() + 1.0 : 0.0);
924 
925 		item->Update(this, &font);
926 
927 		_FixupScrollBar();
928 		InvalidateItem(CountItems() - 1);
929 	}
930 
931 	return true;
932 }
933 
934 
935 bool
936 BListView::AddList(BList* list, int32 index)
937 {
938 	if (!fList.AddList(list, index))
939 		return false;
940 
941 	int32 count = list->CountItems();
942 
943 	if (fFirstSelected != -1 && index < fFirstSelected)
944 		fFirstSelected += count;
945 
946 	if (fLastSelected != -1 && index < fLastSelected)
947 		fLastSelected += count;
948 
949 	if (Window()) {
950 		BFont font;
951 		GetFont(&font);
952 
953 		for (int32 i = index; i <= (index + count - 1); i++) {
954 			ItemAt(i)->SetTop((i > 0) ? ItemAt(i - 1)->Bottom() + 1.0 : 0.0);
955 			ItemAt(i)->Update(this, &font);
956 		}
957 
958 		_RecalcItemTops(index + count - 1);
959 
960 		_FixupScrollBar();
961 		Invalidate(); // TODO
962 	}
963 
964 	return true;
965 }
966 
967 
968 bool
969 BListView::AddList(BList* list)
970 {
971 	return AddList(list, CountItems());
972 }
973 
974 
975 BListItem*
976 BListView::RemoveItem(int32 index)
977 {
978 	BListItem* item = ItemAt(index);
979 	if (item == NULL)
980 		return NULL;
981 
982 	if (item->IsSelected())
983 		Deselect(index);
984 
985 	if (!fList.RemoveItem(item))
986 		return NULL;
987 
988 	if (fFirstSelected != -1 && index < fFirstSelected)
989 		fFirstSelected--;
990 
991 	if (fLastSelected != -1 && index < fLastSelected)
992 		fLastSelected--;
993 
994 	if (fAnchorIndex != -1 && index < fAnchorIndex)
995 		fAnchorIndex--;
996 
997 	_RecalcItemTops(index);
998 
999 	_InvalidateFrom(index);
1000 	_FixupScrollBar();
1001 
1002 	return item;
1003 }
1004 
1005 
1006 bool
1007 BListView::RemoveItem(BListItem* item)
1008 {
1009 	return BListView::RemoveItem(IndexOf(item)) != NULL;
1010 }
1011 
1012 
1013 bool
1014 BListView::RemoveItems(int32 index, int32 count)
1015 {
1016 	if (index >= fList.CountItems())
1017 		index = -1;
1018 
1019 	if (index < 0)
1020 		return false;
1021 
1022 	if (fAnchorIndex != -1 && index < fAnchorIndex)
1023 		fAnchorIndex = index;
1024 
1025 	fList.RemoveItems(index, count);
1026 	if (index < fList.CountItems())
1027 		_RecalcItemTops(index);
1028 
1029 	Invalidate();
1030 	return true;
1031 }
1032 
1033 
1034 void
1035 BListView::SetSelectionMessage(BMessage* message)
1036 {
1037 	delete fSelectMessage;
1038 	fSelectMessage = message;
1039 }
1040 
1041 
1042 void
1043 BListView::SetInvocationMessage(BMessage* message)
1044 {
1045 	BInvoker::SetMessage(message);
1046 }
1047 
1048 
1049 BMessage*
1050 BListView::InvocationMessage() const
1051 {
1052 	return BInvoker::Message();
1053 }
1054 
1055 
1056 uint32
1057 BListView::InvocationCommand() const
1058 {
1059 	return BInvoker::Command();
1060 }
1061 
1062 
1063 BMessage*
1064 BListView::SelectionMessage() const
1065 {
1066 	return fSelectMessage;
1067 }
1068 
1069 
1070 uint32
1071 BListView::SelectionCommand() const
1072 {
1073 	if (fSelectMessage)
1074 		return fSelectMessage->what;
1075 
1076 	return 0;
1077 }
1078 
1079 
1080 void
1081 BListView::SetListType(list_view_type type)
1082 {
1083 	if (fListType == B_MULTIPLE_SELECTION_LIST
1084 		&& type == B_SINGLE_SELECTION_LIST) {
1085 		Select(CurrentSelection(0));
1086 	}
1087 
1088 	fListType = type;
1089 }
1090 
1091 
1092 list_view_type
1093 BListView::ListType() const
1094 {
1095 	return fListType;
1096 }
1097 
1098 
1099 BListItem*
1100 BListView::ItemAt(int32 index) const
1101 {
1102 	return (BListItem*)fList.ItemAt(index);
1103 }
1104 
1105 
1106 int32
1107 BListView::IndexOf(BListItem* item) const
1108 {
1109 	if (Window()) {
1110 		if (item != NULL) {
1111 			int32 index = IndexOf(BPoint(0.0, item->Top()));
1112 			if (index >= 0 && fList.ItemAt(index) == item)
1113 				return index;
1114 
1115 			return -1;
1116 		}
1117 	}
1118 	return fList.IndexOf(item);
1119 }
1120 
1121 
1122 int32
1123 BListView::IndexOf(BPoint point) const
1124 {
1125 	int32 low = 0;
1126 	int32 high = fList.CountItems() - 1;
1127 	int32 mid = -1;
1128 	float frameTop = -1.0;
1129 	float frameBottom = 1.0;
1130 
1131 	// binary search the list
1132 	while (high >= low) {
1133 		mid = (low + high) / 2;
1134 		frameTop = ItemAt(mid)->Top();
1135 		frameBottom = ItemAt(mid)->Bottom();
1136 		if (point.y < frameTop)
1137 			high = mid - 1;
1138 		else if (point.y > frameBottom)
1139 			low = mid + 1;
1140 		else
1141 			return mid;
1142 	}
1143 
1144 	return -1;
1145 }
1146 
1147 
1148 BListItem*
1149 BListView::FirstItem() const
1150 {
1151 	return (BListItem*)fList.FirstItem();
1152 }
1153 
1154 
1155 BListItem*
1156 BListView::LastItem() const
1157 {
1158 	return (BListItem*)fList.LastItem();
1159 }
1160 
1161 
1162 bool
1163 BListView::HasItem(BListItem *item) const
1164 {
1165 	return IndexOf(item) != -1;
1166 }
1167 
1168 
1169 int32
1170 BListView::CountItems() const
1171 {
1172 	return fList.CountItems();
1173 }
1174 
1175 
1176 void
1177 BListView::MakeEmpty()
1178 {
1179 	if (fList.IsEmpty())
1180 		return;
1181 
1182 	_DeselectAll(-1, -1);
1183 	fList.MakeEmpty();
1184 
1185 	if (Window()) {
1186 		_FixupScrollBar();
1187 		Invalidate();
1188 	}
1189 }
1190 
1191 
1192 bool
1193 BListView::IsEmpty() const
1194 {
1195 	return fList.IsEmpty();
1196 }
1197 
1198 
1199 void
1200 BListView::DoForEach(bool (*func)(BListItem*))
1201 {
1202 	fList.DoForEach(reinterpret_cast<bool (*)(void*)>(func));
1203 }
1204 
1205 
1206 void
1207 BListView::DoForEach(bool (*func)(BListItem*, void*), void* arg)
1208 {
1209 	fList.DoForEach(reinterpret_cast<bool (*)(void*, void*)>(func), arg);
1210 }
1211 
1212 
1213 const BListItem**
1214 BListView::Items() const
1215 {
1216 	return (const BListItem**)fList.Items();
1217 }
1218 
1219 
1220 void
1221 BListView::InvalidateItem(int32 index)
1222 {
1223 	Invalidate(ItemFrame(index));
1224 }
1225 
1226 
1227 void
1228 BListView::ScrollTo(int32 index)
1229 {
1230 	if (index < 0)
1231 		index = 0;
1232 	if (index > CountItems() - 1)
1233 		index = CountItems() - 1;
1234 
1235 	BRect itemFrame = ItemFrame(index);
1236 	BRect bounds = Bounds();
1237 	if (itemFrame.top < bounds.top)
1238 		BListView::ScrollTo(itemFrame.LeftTop());
1239 	else if (itemFrame.bottom > bounds.bottom)
1240 		BListView::ScrollTo(BPoint(0, itemFrame.bottom - bounds.Height()));
1241 }
1242 
1243 
1244 void
1245 BListView::ScrollToSelection()
1246 {
1247 	BRect itemFrame = ItemFrame(CurrentSelection(0));
1248 
1249 	if (itemFrame.top < Bounds().top
1250 		|| itemFrame.Height() > Bounds().Height())
1251 		ScrollBy(0, itemFrame.top - Bounds().top);
1252 	else if (itemFrame.bottom > Bounds().bottom)
1253 		ScrollBy(0, itemFrame.bottom - Bounds().bottom);
1254 }
1255 
1256 
1257 void
1258 BListView::Select(int32 index, bool extend)
1259 {
1260 	if (_Select(index, extend)) {
1261 		SelectionChanged();
1262 		InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED);
1263 	}
1264 }
1265 
1266 
1267 void
1268 BListView::Select(int32 start, int32 finish, bool extend)
1269 {
1270 	if (_Select(start, finish, extend)) {
1271 		SelectionChanged();
1272 		InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED);
1273 	}
1274 }
1275 
1276 
1277 bool
1278 BListView::IsItemSelected(int32 index) const
1279 {
1280 	BListItem* item = ItemAt(index);
1281 	if (item != NULL)
1282 		return item->IsSelected();
1283 
1284 	return false;
1285 }
1286 
1287 
1288 int32
1289 BListView::CurrentSelection(int32 index) const
1290 {
1291 	if (fFirstSelected == -1)
1292 		return -1;
1293 
1294 	if (index == 0)
1295 		return fFirstSelected;
1296 
1297 	for (int32 i = fFirstSelected; i <= fLastSelected; i++) {
1298 		if (ItemAt(i)->IsSelected()) {
1299 			if (index == 0)
1300 				return i;
1301 
1302 			index--;
1303 		}
1304 	}
1305 
1306 	return -1;
1307 }
1308 
1309 
1310 status_t
1311 BListView::Invoke(BMessage* message)
1312 {
1313 	// Note, this is more or less a copy of BControl::Invoke() and should
1314 	// stay that way (ie. changes done there should be adopted here)
1315 
1316 	bool notify = false;
1317 	uint32 kind = InvokeKind(&notify);
1318 
1319 	BMessage clone(kind);
1320 	status_t err = B_BAD_VALUE;
1321 
1322 	if (!message && !notify)
1323 		message = Message();
1324 
1325 	if (!message) {
1326 		if (!IsWatched())
1327 			return err;
1328 	} else
1329 		clone = *message;
1330 
1331 	clone.AddInt64("when", (int64)system_time());
1332 	clone.AddPointer("source", this);
1333 	clone.AddMessenger("be:sender", BMessenger(this));
1334 
1335 	if (fListType == B_SINGLE_SELECTION_LIST)
1336 		clone.AddInt32("index", fFirstSelected);
1337 	else {
1338 		if (fFirstSelected >= 0) {
1339 			for (int32 i = fFirstSelected; i <= fLastSelected; i++) {
1340 				if (ItemAt(i)->IsSelected())
1341 					clone.AddInt32("index", i);
1342 			}
1343 		}
1344 	}
1345 
1346 	if (message)
1347 		err = BInvoker::Invoke(&clone);
1348 
1349 	SendNotices(kind, &clone);
1350 
1351 	return err;
1352 }
1353 
1354 
1355 void
1356 BListView::DeselectAll()
1357 {
1358 	if (_DeselectAll(-1, -1)) {
1359 		SelectionChanged();
1360 		InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED);
1361 	}
1362 }
1363 
1364 
1365 void
1366 BListView::DeselectExcept(int32 exceptFrom, int32 exceptTo)
1367 {
1368 	if (exceptFrom > exceptTo || exceptFrom < 0 || exceptTo < 0)
1369 		return;
1370 
1371 	if (_DeselectAll(exceptFrom, exceptTo)) {
1372 		SelectionChanged();
1373 		InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED);
1374 	}
1375 }
1376 
1377 
1378 void
1379 BListView::Deselect(int32 index)
1380 {
1381 	if (_Deselect(index)) {
1382 		SelectionChanged();
1383 		InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED);
1384 	}
1385 }
1386 
1387 
1388 void
1389 BListView::SelectionChanged()
1390 {
1391 	// Hook method to be implemented by subclasses
1392 }
1393 
1394 
1395 void
1396 BListView::SortItems(int (*cmp)(const void *, const void *))
1397 {
1398 	if (_DeselectAll(-1, -1)) {
1399 		SelectionChanged();
1400 		InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED);
1401 	}
1402 
1403 	fList.SortItems(cmp);
1404 	_RecalcItemTops(0);
1405 	Invalidate();
1406 }
1407 
1408 
1409 bool
1410 BListView::SwapItems(int32 a, int32 b)
1411 {
1412 	MiscData data;
1413 
1414 	data.swap.a = a;
1415 	data.swap.b = b;
1416 
1417 	return DoMiscellaneous(B_SWAP_OP, &data);
1418 }
1419 
1420 
1421 bool
1422 BListView::MoveItem(int32 from, int32 to)
1423 {
1424 	MiscData data;
1425 
1426 	data.move.from = from;
1427 	data.move.to = to;
1428 
1429 	return DoMiscellaneous(B_MOVE_OP, &data);
1430 }
1431 
1432 
1433 bool
1434 BListView::ReplaceItem(int32 index, BListItem* item)
1435 {
1436 	MiscData data;
1437 
1438 	data.replace.index = index;
1439 	data.replace.item = item;
1440 
1441 	return DoMiscellaneous(B_REPLACE_OP, &data);
1442 }
1443 
1444 
1445 BRect
1446 BListView::ItemFrame(int32 index)
1447 {
1448 	BRect frame = Bounds();
1449 	if (index < 0 || index >= CountItems()) {
1450 		frame.top = 0;
1451 		frame.bottom = -1;
1452 	} else {
1453 		BListItem* item = ItemAt(index);
1454 		frame.top = item->Top();
1455 		frame.bottom = item->Bottom();
1456 	}
1457 	return frame;
1458 }
1459 
1460 
1461 // #pragma mark -
1462 
1463 
1464 BHandler*
1465 BListView::ResolveSpecifier(BMessage* message, int32 index,
1466 	BMessage* specifier, int32 what, const char* property)
1467 {
1468 	BPropertyInfo propInfo(sProperties);
1469 
1470 	if (propInfo.FindMatch(message, 0, specifier, what, property) < 0) {
1471 		return BView::ResolveSpecifier(message, index, specifier, what,
1472 			property);
1473 	}
1474 
1475 	// TODO: msg->AddInt32("_match_code_", );
1476 
1477 	return this;
1478 }
1479 
1480 
1481 status_t
1482 BListView::GetSupportedSuites(BMessage* data)
1483 {
1484 	if (data == NULL)
1485 		return B_BAD_VALUE;
1486 
1487 	status_t err = data->AddString("suites", "suite/vnd.Be-list-view");
1488 
1489 	BPropertyInfo propertyInfo(sProperties);
1490 	if (err == B_OK)
1491 		err = data->AddFlat("messages", &propertyInfo);
1492 
1493 	if (err == B_OK)
1494 		return BView::GetSupportedSuites(data);
1495 	return err;
1496 }
1497 
1498 
1499 status_t
1500 BListView::Perform(perform_code code, void* _data)
1501 {
1502 	switch (code) {
1503 		case PERFORM_CODE_MIN_SIZE:
1504 			((perform_data_min_size*)_data)->return_value
1505 				= BListView::MinSize();
1506 			return B_OK;
1507 		case PERFORM_CODE_MAX_SIZE:
1508 			((perform_data_max_size*)_data)->return_value
1509 				= BListView::MaxSize();
1510 			return B_OK;
1511 		case PERFORM_CODE_PREFERRED_SIZE:
1512 			((perform_data_preferred_size*)_data)->return_value
1513 				= BListView::PreferredSize();
1514 			return B_OK;
1515 		case PERFORM_CODE_LAYOUT_ALIGNMENT:
1516 			((perform_data_layout_alignment*)_data)->return_value
1517 				= BListView::LayoutAlignment();
1518 			return B_OK;
1519 		case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH:
1520 			((perform_data_has_height_for_width*)_data)->return_value
1521 				= BListView::HasHeightForWidth();
1522 			return B_OK;
1523 		case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH:
1524 		{
1525 			perform_data_get_height_for_width* data
1526 				= (perform_data_get_height_for_width*)_data;
1527 			BListView::GetHeightForWidth(data->width, &data->min, &data->max,
1528 				&data->preferred);
1529 			return B_OK;
1530 		}
1531 		case PERFORM_CODE_SET_LAYOUT:
1532 		{
1533 			perform_data_set_layout* data = (perform_data_set_layout*)_data;
1534 			BListView::SetLayout(data->layout);
1535 			return B_OK;
1536 		}
1537 		case PERFORM_CODE_LAYOUT_INVALIDATED:
1538 		{
1539 			perform_data_layout_invalidated* data
1540 				= (perform_data_layout_invalidated*)_data;
1541 			BListView::LayoutInvalidated(data->descendants);
1542 			return B_OK;
1543 		}
1544 		case PERFORM_CODE_DO_LAYOUT:
1545 		{
1546 			BListView::DoLayout();
1547 			return B_OK;
1548 		}
1549 	}
1550 
1551 	return BView::Perform(code, _data);
1552 }
1553 
1554 
1555 bool
1556 BListView::DoMiscellaneous(MiscCode code, MiscData* data)
1557 {
1558 	if (code > B_SWAP_OP)
1559 		return false;
1560 
1561 	switch (code) {
1562 		case B_NO_OP:
1563 			break;
1564 
1565 		case B_REPLACE_OP:
1566 			return _ReplaceItem(data->replace.index, data->replace.item);
1567 
1568 		case B_MOVE_OP:
1569 			return _MoveItem(data->move.from, data->move.to);
1570 
1571 		case B_SWAP_OP:
1572 			return _SwapItems(data->swap.a, data->swap.b);
1573 	}
1574 
1575 	return false;
1576 }
1577 
1578 
1579 // #pragma mark -
1580 
1581 
1582 void BListView::_ReservedListView2() {}
1583 void BListView::_ReservedListView3() {}
1584 void BListView::_ReservedListView4() {}
1585 
1586 
1587 BListView&
1588 BListView::operator=(const BListView& /*other*/)
1589 {
1590 	return *this;
1591 }
1592 
1593 
1594 // #pragma mark -
1595 
1596 
1597 void
1598 BListView::_InitObject(list_view_type type)
1599 {
1600 	fListType = type;
1601 	fFirstSelected = -1;
1602 	fLastSelected = -1;
1603 	fAnchorIndex = -1;
1604 	fSelectMessage = NULL;
1605 	fScrollView = NULL;
1606 
1607 	fTrack = new track_data;
1608 	fTrack->drag_start = B_ORIGIN;
1609 	fTrack->item_index = -1;
1610 	fTrack->buttons = 0;
1611 	fTrack->selected_click_count = 0;
1612 	fTrack->was_selected = false;
1613 	fTrack->try_drag = false;
1614 	fTrack->is_dragging = false;
1615 	fTrack->last_click_time = 0;
1616 
1617 	SetViewUIColor(B_LIST_BACKGROUND_COLOR);
1618 	SetLowUIColor(B_LIST_BACKGROUND_COLOR);
1619 }
1620 
1621 
1622 void
1623 BListView::_FixupScrollBar()
1624 {
1625 
1626 	BScrollBar* vertScroller = ScrollBar(B_VERTICAL);
1627 	if (vertScroller != NULL) {
1628 		BRect bounds = Bounds();
1629 		int32 count = CountItems();
1630 
1631 		float itemHeight = 0.0;
1632 
1633 		if (CountItems() > 0)
1634 			itemHeight = ItemAt(CountItems() - 1)->Bottom();
1635 
1636 		if (bounds.Height() > itemHeight) {
1637 			// no scrolling
1638 			vertScroller->SetRange(0.0, 0.0);
1639 			vertScroller->SetValue(0.0);
1640 				// also scrolls ListView to the top
1641 		} else {
1642 			vertScroller->SetRange(0.0, itemHeight - bounds.Height() - 1.0);
1643 			vertScroller->SetProportion(bounds.Height () / itemHeight);
1644 			// scroll up if there is empty room on bottom
1645 			if (itemHeight < bounds.bottom)
1646 				ScrollBy(0.0, bounds.bottom - itemHeight);
1647 		}
1648 
1649 		if (count != 0)
1650 			vertScroller->SetSteps(
1651 				ceilf(FirstItem()->Height()), bounds.Height());
1652 	}
1653 
1654 	BScrollBar* horizontalScroller = ScrollBar(B_HORIZONTAL);
1655 	if (horizontalScroller != NULL) {
1656 		float w;
1657 		GetPreferredSize(&w, NULL);
1658 		BRect scrollBarSize = horizontalScroller->Bounds();
1659 
1660 		if (w <= scrollBarSize.Width()) {
1661 			// no scrolling
1662 			horizontalScroller->SetRange(0.0, 0.0);
1663 			horizontalScroller->SetValue(0.0);
1664 		} else {
1665 			horizontalScroller->SetRange(0, w - scrollBarSize.Width());
1666 			horizontalScroller->SetProportion(scrollBarSize.Width() / w);
1667 		}
1668 	}
1669 }
1670 
1671 
1672 void
1673 BListView::_InvalidateFrom(int32 index)
1674 {
1675 	// make sure index is behind last valid index
1676 	int32 count = CountItems();
1677 	if (index >= count)
1678 		index = count;
1679 
1680 	// take the item before the wanted one,
1681 	// because that might already be removed
1682 	index--;
1683 	BRect dirty = Bounds();
1684 	if (index >= 0)
1685 		dirty.top = ItemFrame(index).bottom + 1;
1686 
1687 	Invalidate(dirty);
1688 }
1689 
1690 
1691 void
1692 BListView::_UpdateItems()
1693 {
1694 	BFont font;
1695 	GetFont(&font);
1696 	for (int32 i = 0; i < CountItems(); i++) {
1697 		ItemAt(i)->SetTop((i > 0) ? ItemAt(i - 1)->Bottom() + 1.0 : 0.0);
1698 		ItemAt(i)->Update(this, &font);
1699 	}
1700 }
1701 
1702 
1703 /*!	Selects the item at the specified \a index, and returns \c true in
1704 	case the selection was changed because of this method.
1705 	If \a extend is \c false, all previously selected items are deselected.
1706 */
1707 bool
1708 BListView::_Select(int32 index, bool extend)
1709 {
1710 	if (index < 0 || index >= CountItems())
1711 		return false;
1712 
1713 	// only lock the window when there is one
1714 	BAutolock locker(Window());
1715 	if (Window() != NULL && !locker.IsLocked())
1716 		return false;
1717 
1718 	bool changed = false;
1719 
1720 	if (!extend && fFirstSelected != -1)
1721 		changed = _DeselectAll(index, index);
1722 
1723 	fAnchorIndex = index;
1724 
1725 	BListItem* item = ItemAt(index);
1726 	if (!item->IsEnabled() || item->IsSelected()) {
1727 		// if the item is already selected, or can't be selected,
1728 		// we're done here
1729 		return changed;
1730 	}
1731 
1732 	// keep track of first and last selected item
1733 	if (fFirstSelected == -1) {
1734 		// no previous selection
1735 		fFirstSelected = index;
1736 		fLastSelected = index;
1737 	} else if (index < fFirstSelected) {
1738 		fFirstSelected = index;
1739 	} else if (index > fLastSelected) {
1740 		fLastSelected = index;
1741 	}
1742 
1743 	item->Select();
1744 	if (Window() != NULL)
1745 		InvalidateItem(index);
1746 
1747 	return true;
1748 }
1749 
1750 
1751 /*!
1752 	Selects the items between \a from and \a to, and returns \c true in
1753 	case the selection was changed because of this method.
1754 	If \a extend is \c false, all previously selected items are deselected.
1755 */
1756 bool
1757 BListView::_Select(int32 from, int32 to, bool extend)
1758 {
1759 	if (to < from)
1760 		return false;
1761 
1762 	BAutolock locker(Window());
1763 	if (Window() && !locker.IsLocked())
1764 		return false;
1765 
1766 	bool changed = false;
1767 
1768 	if (fFirstSelected != -1 && !extend)
1769 		changed = _DeselectAll(from, to);
1770 
1771 	if (fFirstSelected == -1) {
1772 		fFirstSelected = from;
1773 		fLastSelected = to;
1774 	} else {
1775 		if (from < fFirstSelected)
1776 			fFirstSelected = from;
1777 		if (to > fLastSelected)
1778 			fLastSelected = to;
1779 	}
1780 
1781 	for (int32 i = from; i <= to; ++i) {
1782 		BListItem* item = ItemAt(i);
1783 		if (item != NULL && !item->IsSelected() && item->IsEnabled()) {
1784 			item->Select();
1785 			if (Window() != NULL)
1786 				InvalidateItem(i);
1787 			changed = true;
1788 		}
1789 	}
1790 
1791 	return changed;
1792 }
1793 
1794 
1795 bool
1796 BListView::_Deselect(int32 index)
1797 {
1798 	if (index < 0 || index >= CountItems())
1799 		return false;
1800 
1801 	BWindow* window = Window();
1802 	BAutolock locker(window);
1803 	if (window != NULL && !locker.IsLocked())
1804 		return false;
1805 
1806 	BListItem* item = ItemAt(index);
1807 
1808 	if (item != NULL && item->IsSelected()) {
1809 		BRect frame(ItemFrame(index));
1810 		BRect bounds(Bounds());
1811 
1812 		item->Deselect();
1813 
1814 		if (fFirstSelected == index && fLastSelected == index) {
1815 			fFirstSelected = -1;
1816 			fLastSelected = -1;
1817 		} else {
1818 			if (fFirstSelected == index)
1819 				fFirstSelected = _CalcFirstSelected(index);
1820 
1821 			if (fLastSelected == index)
1822 				fLastSelected = _CalcLastSelected(index);
1823 		}
1824 
1825 		if (window && bounds.Intersects(frame))
1826 			DrawItem(ItemAt(index), frame, true);
1827 	}
1828 
1829 	return true;
1830 }
1831 
1832 
1833 bool
1834 BListView::_DeselectAll(int32 exceptFrom, int32 exceptTo)
1835 {
1836 	if (fFirstSelected == -1)
1837 		return false;
1838 
1839 	BAutolock locker(Window());
1840 	if (Window() && !locker.IsLocked())
1841 		return false;
1842 
1843 	bool changed = false;
1844 
1845 	for (int32 index = fFirstSelected; index <= fLastSelected; index++) {
1846 		// don't deselect the items we shouldn't deselect
1847 		if (exceptFrom != -1 && exceptFrom <= index && exceptTo >= index)
1848 			continue;
1849 
1850 		BListItem* item = ItemAt(index);
1851 		if (item != NULL && item->IsSelected()) {
1852 			item->Deselect();
1853 			InvalidateItem(index);
1854 			changed = true;
1855 		}
1856 	}
1857 
1858 	if (!changed)
1859 		return false;
1860 
1861 	if (exceptFrom != -1) {
1862 		fFirstSelected = _CalcFirstSelected(exceptFrom);
1863 		fLastSelected = _CalcLastSelected(exceptTo);
1864 	} else
1865 		fFirstSelected = fLastSelected = -1;
1866 
1867 	return true;
1868 }
1869 
1870 
1871 int32
1872 BListView::_CalcFirstSelected(int32 after)
1873 {
1874 	if (after >= CountItems())
1875 		return -1;
1876 
1877 	int32 count = CountItems();
1878 	for (int32 i = after; i < count; i++) {
1879 		if (ItemAt(i)->IsSelected())
1880 			return i;
1881 	}
1882 
1883 	return -1;
1884 }
1885 
1886 
1887 int32
1888 BListView::_CalcLastSelected(int32 before)
1889 {
1890 	if (before < 0)
1891 		return -1;
1892 
1893 	before = std::min(CountItems() - 1, before);
1894 
1895 	for (int32 i = before; i >= 0; i--) {
1896 		if (ItemAt(i)->IsSelected())
1897 			return i;
1898 	}
1899 
1900 	return -1;
1901 }
1902 
1903 
1904 void
1905 BListView::DrawItem(BListItem* item, BRect itemRect, bool complete)
1906 {
1907 	if (!item->IsEnabled()) {
1908 		rgb_color textColor = ui_color(B_LIST_ITEM_TEXT_COLOR);
1909 		rgb_color disabledColor;
1910 		if (textColor.red + textColor.green + textColor.blue > 128 * 3)
1911 			disabledColor = tint_color(textColor, B_DARKEN_2_TINT);
1912 		else
1913 			disabledColor = tint_color(textColor, B_LIGHTEN_2_TINT);
1914 
1915 		SetHighColor(disabledColor);
1916 	} else if (item->IsSelected())
1917 		SetHighColor(ui_color(B_LIST_SELECTED_ITEM_TEXT_COLOR));
1918 	else
1919 		SetHighColor(ui_color(B_LIST_ITEM_TEXT_COLOR));
1920 
1921 	item->DrawItem(this, itemRect, complete);
1922 }
1923 
1924 
1925 bool
1926 BListView::_SwapItems(int32 a, int32 b)
1927 {
1928 	// remember frames of items before anything happens,
1929 	// the tricky situation is when the two items have
1930 	// a different height
1931 	BRect aFrame = ItemFrame(a);
1932 	BRect bFrame = ItemFrame(b);
1933 
1934 	if (!fList.SwapItems(a, b))
1935 		return false;
1936 
1937 	if (a == b) {
1938 		// nothing to do, but success nevertheless
1939 		return true;
1940 	}
1941 
1942 	// track anchor item
1943 	if (fAnchorIndex == a)
1944 		fAnchorIndex = b;
1945 	else if (fAnchorIndex == b)
1946 		fAnchorIndex = a;
1947 
1948 	// track selection
1949 	// NOTE: this is only important if the selection status
1950 	// of both items is not the same
1951 	int32 first = std::min(a, b);
1952 	int32 last = std::max(a, b);
1953 	if (ItemAt(a)->IsSelected() != ItemAt(b)->IsSelected()) {
1954 		if (first < fFirstSelected || last > fLastSelected) {
1955 			_RescanSelection(std::min(first, fFirstSelected),
1956 				std::max(last, fLastSelected));
1957 		}
1958 		// though the actually selected items stayed the
1959 		// same, the selection has still changed
1960 		SelectionChanged();
1961 	}
1962 
1963 	ItemAt(a)->SetTop(aFrame.top);
1964 	ItemAt(b)->SetTop(bFrame.top);
1965 
1966 	// take care of invalidation
1967 	if (Window()) {
1968 		// NOTE: window looper is assumed to be locked!
1969 		if (aFrame.Height() != bFrame.Height()) {
1970 			_RecalcItemTops(first, last);
1971 			// items in between shifted visually
1972 			Invalidate(aFrame | bFrame);
1973 		} else {
1974 			Invalidate(aFrame);
1975 			Invalidate(bFrame);
1976 		}
1977 	}
1978 
1979 	return true;
1980 }
1981 
1982 
1983 bool
1984 BListView::_MoveItem(int32 from, int32 to)
1985 {
1986 	// remember item frames before doing anything
1987 	BRect frameFrom = ItemFrame(from);
1988 	BRect frameTo = ItemFrame(to);
1989 
1990 	if (!fList.MoveItem(from, to))
1991 		return false;
1992 
1993 	// track anchor item
1994 	if (fAnchorIndex == from)
1995 		fAnchorIndex = to;
1996 
1997 	// track selection
1998 	if (ItemAt(to)->IsSelected()) {
1999 		_RescanSelection(from, to);
2000 		// though the actually selected items stayed the
2001 		// same, the selection has still changed
2002 		SelectionChanged();
2003 	}
2004 
2005 	_RecalcItemTops((to > from) ? from : to);
2006 
2007 	// take care of invalidation
2008 	if (Window()) {
2009 		// NOTE: window looper is assumed to be locked!
2010 		Invalidate(frameFrom | frameTo);
2011 	}
2012 
2013 	return true;
2014 }
2015 
2016 
2017 bool
2018 BListView::_ReplaceItem(int32 index, BListItem* item)
2019 {
2020 	if (item == NULL)
2021 		return false;
2022 
2023 	BListItem* old = ItemAt(index);
2024 	if (!old)
2025 		return false;
2026 
2027 	BRect frame = ItemFrame(index);
2028 
2029 	bool selectionChanged = old->IsSelected() != item->IsSelected();
2030 
2031 	// replace item
2032 	if (!fList.ReplaceItem(index, item))
2033 		return false;
2034 
2035 	// tack selection
2036 	if (selectionChanged) {
2037 		int32 start = std::min(fFirstSelected, index);
2038 		int32 end = std::max(fLastSelected, index);
2039 		_RescanSelection(start, end);
2040 		SelectionChanged();
2041 	}
2042 	_RecalcItemTops(index);
2043 
2044 	bool itemHeightChanged = frame != ItemFrame(index);
2045 
2046 	// take care of invalidation
2047 	if (Window()) {
2048 		// NOTE: window looper is assumed to be locked!
2049 		if (itemHeightChanged)
2050 			_InvalidateFrom(index);
2051 		else
2052 			Invalidate(frame);
2053 	}
2054 
2055 	if (itemHeightChanged)
2056 		_FixupScrollBar();
2057 
2058 	return true;
2059 }
2060 
2061 
2062 void
2063 BListView::_RescanSelection(int32 from, int32 to)
2064 {
2065 	if (from > to) {
2066 		int32 tmp = from;
2067 		from = to;
2068 		to = tmp;
2069 	}
2070 
2071 	from = std::max((int32)0, from);
2072 	to = std::min(to, CountItems() - 1);
2073 
2074 	if (fAnchorIndex != -1) {
2075 		if (fAnchorIndex == from)
2076 			fAnchorIndex = to;
2077 		else if (fAnchorIndex == to)
2078 			fAnchorIndex = from;
2079 	}
2080 
2081 	for (int32 i = from; i <= to; i++) {
2082 		if (ItemAt(i)->IsSelected()) {
2083 			fFirstSelected = i;
2084 			break;
2085 		}
2086 	}
2087 
2088 	if (fFirstSelected > from)
2089 		from = fFirstSelected;
2090 
2091 	fLastSelected = fFirstSelected;
2092 	for (int32 i = from; i <= to; i++) {
2093 		if (ItemAt(i)->IsSelected())
2094 			fLastSelected = i;
2095 	}
2096 }
2097 
2098 
2099 void
2100 BListView::_RecalcItemTops(int32 start, int32 end)
2101 {
2102 	int32 count = CountItems();
2103 	if ((start < 0) || (start >= count))
2104 		return;
2105 
2106 	if (end >= 0)
2107 		count = end + 1;
2108 
2109 	float top = (start == 0) ? 0.0 : ItemAt(start - 1)->Bottom() + 1.0;
2110 
2111 	for (int32 i = start; i < count; i++) {
2112 		BListItem *item = ItemAt(i);
2113 		item->SetTop(top);
2114 		top += ceilf(item->Height());
2115 	}
2116 }
2117 
2118 
2119 void
2120 BListView::_DoSelection(int32 index)
2121 {
2122 	BListItem* item = ItemAt(index);
2123 
2124 	// don't alter selection if invalid item clicked
2125 	if (index < 0 || item == NULL)
2126 		return;
2127 
2128 	// deselect all if clicked on disabled
2129 	if (!item->IsEnabled())
2130 		return DeselectAll();
2131 
2132 	if (fListType == B_MULTIPLE_SELECTION_LIST) {
2133 		// multiple-selection list
2134 
2135 		if ((modifiers() & B_SHIFT_KEY) != 0) {
2136 			if (index >= fFirstSelected && index < fLastSelected) {
2137 				// clicked inside of selected items block, deselect all
2138 				// except from the first selected index to item index
2139 				DeselectExcept(fFirstSelected, index);
2140 			} else {
2141 				// extend or contract selection
2142 				Select(std::min(index, fFirstSelected),
2143 					std::max(index, fLastSelected));
2144 			}
2145 		} else if ((modifiers() & B_COMMAND_KEY) != 0) {
2146 			// toggle selection state
2147 			if (item->IsSelected())
2148 				Deselect(index);
2149 			else
2150 				Select(index, true);
2151 		} else if (fTrack->selected_click_count != 1)
2152 			Select(index); // eat a click on selected for drag and drop
2153 	} else {
2154 		// single-selection list
2155 
2156 		// toggle selection state
2157 		if ((modifiers() & B_COMMAND_KEY) != 0 && item->IsSelected())
2158 			Deselect(index);
2159 		else
2160 			Select(index);
2161 	}
2162 }
2163