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