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