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