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