xref: /haiku/src/kits/interface/ListView.cpp (revision f290b766707b386d72e2eaadd35cc3d999405077)
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 	}
1519 }
1520 
1521 
1522 void
1523 BListView::_InvalidateFrom(int32 index)
1524 {
1525 	// make sure index is behind last valid index
1526 	int32 count = CountItems();
1527 	if (index >= count)
1528 		index = count;
1529 
1530 	// take the item before the wanted one,
1531 	// because that might already be removed
1532 	index--;
1533 	BRect dirty = Bounds();
1534 	if (index >= 0)
1535 		dirty.top = ItemFrame(index).bottom + 1;
1536 
1537 	Invalidate(dirty);
1538 }
1539 
1540 
1541 void
1542 BListView::_UpdateItems()
1543 {
1544 	BFont font;
1545 	GetFont(&font);
1546 	for (int32 i = 0; i < CountItems(); i++) {
1547 		ItemAt(i)->SetTop((i > 0) ? ItemAt(i - 1)->Bottom() + 1.0 : 0.0);
1548 		ItemAt(i)->Update(this, &font);
1549 	}
1550 }
1551 
1552 
1553 /*!	Selects the item at the specified \a index, and returns \c true in
1554 	case the selection was changed because of this method.
1555 	If \a extend is \c false, all previously selected items are deselected.
1556 */
1557 bool
1558 BListView::_Select(int32 index, bool extend)
1559 {
1560 	if (index < 0 || index >= CountItems())
1561 		return false;
1562 
1563 	// only lock the window when there is one
1564 	BAutolock locker(Window());
1565 	if (Window() != NULL && !locker.IsLocked())
1566 		return false;
1567 
1568 	bool changed = false;
1569 
1570 	if (!extend && fFirstSelected != -1)
1571 		changed = _DeselectAll(index, index);
1572 
1573 	fAnchorIndex = index;
1574 
1575 	BListItem* item = ItemAt(index);
1576 	if (!item->IsEnabled() || item->IsSelected()) {
1577 		// if the item is already selected, or can't be selected,
1578 		// we're done here
1579 		return changed;
1580 	}
1581 
1582 	// keep track of first and last selected item
1583 	if (fFirstSelected == -1) {
1584 		// no previous selection
1585 		fFirstSelected = index;
1586 		fLastSelected = index;
1587 	} else if (index < fFirstSelected) {
1588 		fFirstSelected = index;
1589 	} else if (index > fLastSelected) {
1590 		fLastSelected = index;
1591 	}
1592 
1593 	item->Select();
1594 	if (Window() != NULL)
1595 		InvalidateItem(index);
1596 
1597 	return true;
1598 }
1599 
1600 
1601 /*!
1602 	Selects the items between \a from and \a to, and returns \c true in
1603 	case the selection was changed because of this method.
1604 	If \a extend is \c false, all previously selected items are deselected.
1605 */
1606 bool
1607 BListView::_Select(int32 from, int32 to, bool extend)
1608 {
1609 	if (to < from)
1610 		return false;
1611 
1612 	BAutolock locker(Window());
1613 	if (Window() && !locker.IsLocked())
1614 		return false;
1615 
1616 	bool changed = false;
1617 
1618 	if (fFirstSelected != -1 && !extend)
1619 		changed = _DeselectAll(from, to);
1620 
1621 	if (fFirstSelected == -1) {
1622 		fFirstSelected = from;
1623 		fLastSelected = to;
1624 	} else {
1625 		if (from < fFirstSelected)
1626 			fFirstSelected = from;
1627 		if (to > fLastSelected)
1628 			fLastSelected = to;
1629 	}
1630 
1631 	for (int32 i = from; i <= to; ++i) {
1632 		BListItem* item = ItemAt(i);
1633 		if (item != NULL && !item->IsSelected() && item->IsEnabled()) {
1634 			item->Select();
1635 			if (Window() != NULL)
1636 				InvalidateItem(i);
1637 			changed = true;
1638 		}
1639 	}
1640 
1641 	return changed;
1642 }
1643 
1644 
1645 bool
1646 BListView::_Deselect(int32 index)
1647 {
1648 	if (index < 0 || index >= CountItems())
1649 		return false;
1650 
1651 	BWindow* window = Window();
1652 	BAutolock locker(window);
1653 	if (window != NULL && !locker.IsLocked())
1654 		return false;
1655 
1656 	BListItem* item = ItemAt(index);
1657 
1658 	if (item != NULL && item->IsSelected()) {
1659 		BRect frame(ItemFrame(index));
1660 		BRect bounds(Bounds());
1661 
1662 		item->Deselect();
1663 
1664 		if (fFirstSelected == index && fLastSelected == index) {
1665 			fFirstSelected = -1;
1666 			fLastSelected = -1;
1667 		} else {
1668 			if (fFirstSelected == index)
1669 				fFirstSelected = _CalcFirstSelected(index);
1670 
1671 			if (fLastSelected == index)
1672 				fLastSelected = _CalcLastSelected(index);
1673 		}
1674 
1675 		if (window && bounds.Intersects(frame))
1676 			DrawItem(ItemAt(index), frame, true);
1677 	}
1678 
1679 	return true;
1680 }
1681 
1682 
1683 bool
1684 BListView::_DeselectAll(int32 exceptFrom, int32 exceptTo)
1685 {
1686 	if (fFirstSelected == -1)
1687 		return false;
1688 
1689 	BAutolock locker(Window());
1690 	if (Window() && !locker.IsLocked())
1691 		return false;
1692 
1693 	bool changed = false;
1694 
1695 	for (int32 index = fFirstSelected; index <= fLastSelected; index++) {
1696 		// don't deselect the items we shouldn't deselect
1697 		if (exceptFrom != -1 && exceptFrom <= index && exceptTo >= index)
1698 			continue;
1699 
1700 		BListItem* item = ItemAt(index);
1701 		if (item != NULL && item->IsSelected()) {
1702 			item->Deselect();
1703 			InvalidateItem(index);
1704 			changed = true;
1705 		}
1706 	}
1707 
1708 	if (!changed)
1709 		return false;
1710 
1711 	if (exceptFrom != -1) {
1712 		fFirstSelected = _CalcFirstSelected(exceptFrom);
1713 		fLastSelected = _CalcLastSelected(exceptTo);
1714 	} else
1715 		fFirstSelected = fLastSelected = -1;
1716 
1717 	return true;
1718 }
1719 
1720 
1721 int32
1722 BListView::_CalcFirstSelected(int32 after)
1723 {
1724 	if (after >= CountItems())
1725 		return -1;
1726 
1727 	int32 count = CountItems();
1728 	for (int32 i = after; i < count; i++) {
1729 		if (ItemAt(i)->IsSelected())
1730 			return i;
1731 	}
1732 
1733 	return -1;
1734 }
1735 
1736 
1737 int32
1738 BListView::_CalcLastSelected(int32 before)
1739 {
1740 	if (before < 0)
1741 		return -1;
1742 
1743 	before = std::min(CountItems() - 1, before);
1744 
1745 	for (int32 i = before; i >= 0; i--) {
1746 		if (ItemAt(i)->IsSelected())
1747 			return i;
1748 	}
1749 
1750 	return -1;
1751 }
1752 
1753 
1754 void
1755 BListView::DrawItem(BListItem* item, BRect itemRect, bool complete)
1756 {
1757 	if (!item->IsEnabled()) {
1758 		rgb_color textColor = ui_color(B_LIST_ITEM_TEXT_COLOR);
1759 		rgb_color disabledColor;
1760 		if (textColor.red + textColor.green + textColor.blue > 128 * 3)
1761 			disabledColor = tint_color(textColor, B_DARKEN_2_TINT);
1762 		else
1763 			disabledColor = tint_color(textColor, B_LIGHTEN_2_TINT);
1764 
1765 		SetHighColor(disabledColor);
1766 	} else if (item->IsSelected())
1767 		SetHighColor(ui_color(B_LIST_SELECTED_ITEM_TEXT_COLOR));
1768 	else
1769 		SetHighColor(ui_color(B_LIST_ITEM_TEXT_COLOR));
1770 
1771 	item->DrawItem(this, itemRect, complete);
1772 }
1773 
1774 
1775 bool
1776 BListView::_SwapItems(int32 a, int32 b)
1777 {
1778 	// remember frames of items before anything happens,
1779 	// the tricky situation is when the two items have
1780 	// a different height
1781 	BRect aFrame = ItemFrame(a);
1782 	BRect bFrame = ItemFrame(b);
1783 
1784 	if (!fList.SwapItems(a, b))
1785 		return false;
1786 
1787 	if (a == b) {
1788 		// nothing to do, but success nevertheless
1789 		return true;
1790 	}
1791 
1792 	// track anchor item
1793 	if (fAnchorIndex == a)
1794 		fAnchorIndex = b;
1795 	else if (fAnchorIndex == b)
1796 		fAnchorIndex = a;
1797 
1798 	// track selection
1799 	// NOTE: this is only important if the selection status
1800 	// of both items is not the same
1801 	int32 first = std::min(a, b);
1802 	int32 last = std::max(a, b);
1803 	if (ItemAt(a)->IsSelected() != ItemAt(b)->IsSelected()) {
1804 		if (first < fFirstSelected || last > fLastSelected) {
1805 			_RescanSelection(std::min(first, fFirstSelected),
1806 				std::max(last, fLastSelected));
1807 		}
1808 		// though the actually selected items stayed the
1809 		// same, the selection has still changed
1810 		SelectionChanged();
1811 	}
1812 
1813 	ItemAt(a)->SetTop(aFrame.top);
1814 	ItemAt(b)->SetTop(bFrame.top);
1815 
1816 	// take care of invalidation
1817 	if (Window()) {
1818 		// NOTE: window looper is assumed to be locked!
1819 		if (aFrame.Height() != bFrame.Height()) {
1820 			_RecalcItemTops(first, last);
1821 			// items in between shifted visually
1822 			Invalidate(aFrame | bFrame);
1823 		} else {
1824 			Invalidate(aFrame);
1825 			Invalidate(bFrame);
1826 		}
1827 	}
1828 
1829 	return true;
1830 }
1831 
1832 
1833 bool
1834 BListView::_MoveItem(int32 from, int32 to)
1835 {
1836 	// remember item frames before doing anything
1837 	BRect frameFrom = ItemFrame(from);
1838 	BRect frameTo = ItemFrame(to);
1839 
1840 	if (!fList.MoveItem(from, to))
1841 		return false;
1842 
1843 	// track anchor item
1844 	if (fAnchorIndex == from)
1845 		fAnchorIndex = to;
1846 
1847 	// track selection
1848 	if (ItemAt(to)->IsSelected()) {
1849 		_RescanSelection(from, to);
1850 		// though the actually selected items stayed the
1851 		// same, the selection has still changed
1852 		SelectionChanged();
1853 	}
1854 
1855 	_RecalcItemTops((to > from) ? from : to);
1856 
1857 	// take care of invalidation
1858 	if (Window()) {
1859 		// NOTE: window looper is assumed to be locked!
1860 		Invalidate(frameFrom | frameTo);
1861 	}
1862 
1863 	return true;
1864 }
1865 
1866 
1867 bool
1868 BListView::_ReplaceItem(int32 index, BListItem* item)
1869 {
1870 	if (item == NULL)
1871 		return false;
1872 
1873 	BListItem* old = ItemAt(index);
1874 	if (!old)
1875 		return false;
1876 
1877 	BRect frame = ItemFrame(index);
1878 
1879 	bool selectionChanged = old->IsSelected() != item->IsSelected();
1880 
1881 	// replace item
1882 	if (!fList.ReplaceItem(index, item))
1883 		return false;
1884 
1885 	// tack selection
1886 	if (selectionChanged) {
1887 		int32 start = std::min(fFirstSelected, index);
1888 		int32 end = std::max(fLastSelected, index);
1889 		_RescanSelection(start, end);
1890 		SelectionChanged();
1891 	}
1892 	_RecalcItemTops(index);
1893 
1894 	bool itemHeightChanged = frame != ItemFrame(index);
1895 
1896 	// take care of invalidation
1897 	if (Window()) {
1898 		// NOTE: window looper is assumed to be locked!
1899 		if (itemHeightChanged)
1900 			_InvalidateFrom(index);
1901 		else
1902 			Invalidate(frame);
1903 	}
1904 
1905 	if (itemHeightChanged)
1906 		_FixupScrollBar();
1907 
1908 	return true;
1909 }
1910 
1911 
1912 void
1913 BListView::_RescanSelection(int32 from, int32 to)
1914 {
1915 	if (from > to) {
1916 		int32 tmp = from;
1917 		from = to;
1918 		to = tmp;
1919 	}
1920 
1921 	from = std::max((int32)0, from);
1922 	to = std::min(to, CountItems() - 1);
1923 
1924 	if (fAnchorIndex != -1) {
1925 		if (fAnchorIndex == from)
1926 			fAnchorIndex = to;
1927 		else if (fAnchorIndex == to)
1928 			fAnchorIndex = from;
1929 	}
1930 
1931 	for (int32 i = from; i <= to; i++) {
1932 		if (ItemAt(i)->IsSelected()) {
1933 			fFirstSelected = i;
1934 			break;
1935 		}
1936 	}
1937 
1938 	if (fFirstSelected > from)
1939 		from = fFirstSelected;
1940 
1941 	fLastSelected = fFirstSelected;
1942 	for (int32 i = from; i <= to; i++) {
1943 		if (ItemAt(i)->IsSelected())
1944 			fLastSelected = i;
1945 	}
1946 }
1947 
1948 
1949 void
1950 BListView::_RecalcItemTops(int32 start, int32 end)
1951 {
1952 	int32 count = CountItems();
1953 	if ((start < 0) || (start >= count))
1954 		return;
1955 
1956 	if (end >= 0)
1957 		count = end + 1;
1958 
1959 	float top = (start == 0) ? 0.0 : ItemAt(start - 1)->Bottom() + 1.0;
1960 
1961 	for (int32 i = start; i < count; i++) {
1962 		BListItem *item = ItemAt(i);
1963 		item->SetTop(top);
1964 		top += ceilf(item->Height());
1965 	}
1966 }
1967 
1968 
1969 void
1970 BListView::_DoneTracking(BPoint where)
1971 {
1972 	fTrack->try_drag = false;
1973 	fTrack->is_dragging = false;
1974 }
1975 
1976 
1977 void
1978 BListView::_Track(BPoint where, uint32)
1979 {
1980 	if (fTrack->item_index >= 0 && fTrack->try_drag) {
1981 		// initiate a drag if the mouse was moved far enough
1982 		BPoint offset = where - fTrack->drag_start;
1983 		float dragDistance = sqrtf(offset.x * offset.x + offset.y * offset.y);
1984 		if (dragDistance >= 5.0f) {
1985 			fTrack->try_drag = false;
1986 			fTrack->is_dragging = InitiateDrag(fTrack->drag_start,
1987 				fTrack->item_index, fTrack->was_selected);
1988 		}
1989 	}
1990 }
1991