xref: /haiku/src/kits/interface/ListView.cpp (revision cb837539f5d245cedff238f8894c0ae326c2eaf5)
1 /*
2  * Copyright 2001-2015 Haiku, Inc. All rights resrerved.
3  * Distributed under the terms of the MIT license.
4  *
5  * Authors:
6  *		Stephan Assmus, superstippi@gmx.de
7  *		Axel Dörfler, axeld@pinc-software.de
8  *		Marc Flerackers, mflerackers@androme.be
9  *		Rene Gollent, rene@gollent.com
10  *		Ulrich Wimboeck
11  */
12 
13 
14 #include <ListView.h>
15 
16 #include <algorithm>
17 
18 #include <stdio.h>
19 
20 #include <Autolock.h>
21 #include <LayoutUtils.h>
22 #include <PropertyInfo.h>
23 #include <ScrollBar.h>
24 #include <ScrollView.h>
25 #include <Thread.h>
26 #include <Window.h>
27 
28 #include <binary_compatibility/Interface.h>
29 
30 
31 struct track_data {
32 	BPoint		drag_start;
33 	int32		item_index;
34 	bool		was_selected;
35 	bool		try_drag;
36 	bool		is_dragging;
37 	bigtime_t	last_click_time;
38 };
39 
40 
41 const float kDoubleClickThreshold = 6.0f;
42 
43 
44 static property_info sProperties[] = {
45 	{ "Item", { B_COUNT_PROPERTIES, 0 }, { B_DIRECT_SPECIFIER, 0 },
46 		"Returns the number of BListItems currently in the list.", 0,
47 		{ B_INT32_TYPE }
48 	},
49 
50 	{ "Item", { B_EXECUTE_PROPERTY, 0 }, { B_INDEX_SPECIFIER,
51 		B_REVERSE_INDEX_SPECIFIER, B_RANGE_SPECIFIER,
52 		B_REVERSE_RANGE_SPECIFIER, 0 },
53 		"Select and invoke the specified items, first removing any existing "
54 		"selection."
55 	},
56 
57 	{ "Selection", { B_COUNT_PROPERTIES, 0 }, { B_DIRECT_SPECIFIER, 0 },
58 		"Returns int32 count of items in the selection.", 0, { B_INT32_TYPE }
59 	},
60 
61 	{ "Selection", { B_EXECUTE_PROPERTY, 0 }, { B_DIRECT_SPECIFIER, 0 },
62 		"Invoke items in selection."
63 	},
64 
65 	{ "Selection", { B_GET_PROPERTY, 0 }, { B_DIRECT_SPECIFIER, 0 },
66 		"Returns int32 indices of all items in the selection.", 0,
67 		{ B_INT32_TYPE }
68 	},
69 
70 	{ "Selection", { B_SET_PROPERTY, 0 }, { B_INDEX_SPECIFIER,
71 		B_REVERSE_INDEX_SPECIFIER, B_RANGE_SPECIFIER,
72 		B_REVERSE_RANGE_SPECIFIER, 0 },
73 		"Extends current selection or deselects specified items. Boolean field "
74 		"\"data\" chooses selection or deselection.", 0, { B_BOOL_TYPE }
75 	},
76 
77 	{ "Selection", { B_SET_PROPERTY, 0 }, { B_DIRECT_SPECIFIER, 0 },
78 		"Select or deselect all items in the selection. Boolean field \"data\" "
79 		"chooses selection or deselection.", 0, { B_BOOL_TYPE }
80 	},
81 
82 	{ 0 }
83 };
84 
85 
86 BListView::BListView(BRect frame, const char* name, list_view_type type,
87 	uint32 resizingMode, uint32 flags)
88 	:
89 	BView(frame, name, resizingMode, flags | B_SCROLL_VIEW_AWARE)
90 {
91 	_InitObject(type);
92 }
93 
94 
95 BListView::BListView(const char* name, list_view_type type, uint32 flags)
96 	:
97 	BView(name, flags | B_SCROLL_VIEW_AWARE)
98 {
99 	_InitObject(type);
100 }
101 
102 
103 BListView::BListView(list_view_type type)
104 	:
105 	BView(NULL, B_WILL_DRAW | B_FRAME_EVENTS | B_NAVIGABLE
106 		| B_SCROLL_VIEW_AWARE)
107 {
108 	_InitObject(type);
109 }
110 
111 
112 BListView::BListView(BMessage* archive)
113 	:
114 	BView(archive)
115 {
116 	int32 listType;
117 	archive->FindInt32("_lv_type", &listType);
118 	_InitObject((list_view_type)listType);
119 
120 	int32 i = 0;
121 	BMessage subData;
122 	while (archive->FindMessage("_l_items", i++, &subData) == B_OK) {
123 		BArchivable* object = instantiate_object(&subData);
124 		if (object == NULL)
125 			continue;
126 
127 		BListItem* item = dynamic_cast<BListItem*>(object);
128 		if (item != NULL)
129 			AddItem(item);
130 	}
131 
132 	if (archive->HasMessage("_msg")) {
133 		BMessage* invokationMessage = new BMessage;
134 
135 		archive->FindMessage("_msg", invokationMessage);
136 		SetInvocationMessage(invokationMessage);
137 	}
138 
139 	if (archive->HasMessage("_2nd_msg")) {
140 		BMessage* selectionMessage = new BMessage;
141 
142 		archive->FindMessage("_2nd_msg", selectionMessage);
143 		SetSelectionMessage(selectionMessage);
144 	}
145 }
146 
147 
148 BListView::~BListView()
149 {
150 	// NOTE: According to BeBook, BListView does not free the items itself.
151 	delete fTrack;
152 	SetSelectionMessage(NULL);
153 }
154 
155 
156 // #pragma mark -
157 
158 
159 BArchivable*
160 BListView::Instantiate(BMessage* archive)
161 {
162 	if (validate_instantiation(archive, "BListView"))
163 		return new BListView(archive);
164 
165 	return NULL;
166 }
167 
168 
169 status_t
170 BListView::Archive(BMessage* data, bool deep) const
171 {
172 	status_t status = BView::Archive(data, deep);
173 	if (status < B_OK)
174 		return status;
175 
176 	status = data->AddInt32("_lv_type", fListType);
177 	if (status == B_OK && deep) {
178 		BListItem* item;
179 		int32 i = 0;
180 
181 		while ((item = ItemAt(i++)) != NULL) {
182 			BMessage subData;
183 			status = item->Archive(&subData, true);
184 			if (status >= B_OK)
185 				status = data->AddMessage("_l_items", &subData);
186 
187 			if (status < B_OK)
188 				break;
189 		}
190 	}
191 
192 	if (status >= B_OK && InvocationMessage() != NULL)
193 		status = data->AddMessage("_msg", InvocationMessage());
194 
195 	if (status == B_OK && fSelectMessage != NULL)
196 		status = data->AddMessage("_2nd_msg", fSelectMessage);
197 
198 	return status;
199 }
200 
201 
202 // #pragma mark -
203 
204 
205 void
206 BListView::Draw(BRect updateRect)
207 {
208 	int32 count = CountItems();
209 	if (count == 0)
210 		return;
211 
212 	BRect itemFrame(0, 0, Bounds().right, -1);
213 	for (int i = 0; i < count; i++) {
214 		BListItem* item = ItemAt(i);
215 		itemFrame.bottom = itemFrame.top + ceilf(item->Height()) - 1;
216 
217 		if (itemFrame.Intersects(updateRect))
218 			DrawItem(item, itemFrame);
219 
220 		itemFrame.top = itemFrame.bottom + 1;
221 	}
222 }
223 
224 
225 void
226 BListView::AttachedToWindow()
227 {
228 	BView::AttachedToWindow();
229 	_UpdateItems();
230 
231 	if (!Messenger().IsValid())
232 		SetTarget(Window(), NULL);
233 
234 	_FixupScrollBar();
235 }
236 
237 
238 void
239 BListView::DetachedFromWindow()
240 {
241 	BView::DetachedFromWindow();
242 }
243 
244 
245 void
246 BListView::AllAttached()
247 {
248 	BView::AllAttached();
249 }
250 
251 
252 void
253 BListView::AllDetached()
254 {
255 	BView::AllDetached();
256 }
257 
258 
259 void
260 BListView::FrameResized(float newWidth, float newHeight)
261 {
262 	_FixupScrollBar();
263 
264 	// notify items of new width.
265 	_UpdateItems();
266 }
267 
268 
269 void
270 BListView::FrameMoved(BPoint newPosition)
271 {
272 	BView::FrameMoved(newPosition);
273 }
274 
275 
276 void
277 BListView::TargetedByScrollView(BScrollView* view)
278 {
279 	fScrollView = view;
280 	// TODO: We could SetFlags(Flags() | B_FRAME_EVENTS) here, but that
281 	// may mess up application code which manages this by some other means
282 	// and doesn't want us to be messing with flags.
283 }
284 
285 
286 void
287 BListView::WindowActivated(bool active)
288 {
289 	BView::WindowActivated(active);
290 }
291 
292 
293 // #pragma mark -
294 
295 
296 void
297 BListView::MessageReceived(BMessage* message)
298 {
299 	switch (message->what) {
300 		case B_MOUSE_WHEEL_CHANGED:
301 			if (!fTrack->is_dragging)
302 				BView::MessageReceived(message);
303 			break;
304 
305 		case B_COUNT_PROPERTIES:
306 		case B_EXECUTE_PROPERTY:
307 		case B_GET_PROPERTY:
308 		case B_SET_PROPERTY:
309 		{
310 			BPropertyInfo propInfo(sProperties);
311 			BMessage specifier;
312 			const char* property;
313 
314 			if (message->GetCurrentSpecifier(NULL, &specifier) != B_OK
315 				|| specifier.FindString("property", &property) != B_OK) {
316 				BView::MessageReceived(message);
317 				return;
318 			}
319 
320 			switch (propInfo.FindMatch(message, 0, &specifier, message->what,
321 					property)) {
322 				case B_ERROR:
323 					BView::MessageReceived(message);
324 					break;
325 
326 				case 0:
327 				{
328 					BMessage reply(B_REPLY);
329 					reply.AddInt32("result", CountItems());
330 					reply.AddInt32("error", B_OK);
331 
332 					message->SendReply(&reply);
333 					break;
334 				}
335 
336 				case 1:
337 					break;
338 
339 				case 2:
340 				{
341 					int32 count = 0;
342 
343 					for (int32 i = 0; i < CountItems(); i++) {
344 						if (ItemAt(i)->IsSelected())
345 							count++;
346 					}
347 
348 					BMessage reply(B_REPLY);
349 					reply.AddInt32("result", count);
350 					reply.AddInt32("error", B_OK);
351 
352 					message->SendReply(&reply);
353 					break;
354 				}
355 
356 				case 3:
357 					break;
358 
359 				case 4:
360 				{
361 					BMessage reply (B_REPLY);
362 
363 					for (int32 i = 0; i < CountItems(); i++) {
364 						if (ItemAt(i)->IsSelected())
365 							reply.AddInt32("result", i);
366 					}
367 
368 					reply.AddInt32("error", B_OK);
369 
370 					message->SendReply(&reply);
371 					break;
372 				}
373 
374 				case 5:
375 					break;
376 
377 				case 6:
378 				{
379 					BMessage reply(B_REPLY);
380 
381 					bool select;
382 					if (message->FindBool("data", &select) == B_OK && select)
383 						Select(0, CountItems() - 1, false);
384 					else
385 						DeselectAll();
386 
387 					reply.AddInt32("error", B_OK);
388 
389 					message->SendReply(&reply);
390 					break;
391 				}
392 			}
393 			break;
394 		}
395 
396 		case B_SELECT_ALL:
397 			if (fListType == B_MULTIPLE_SELECTION_LIST)
398 				Select(0, CountItems() - 1, false);
399 			break;
400 
401 		default:
402 			BView::MessageReceived(message);
403 	}
404 }
405 
406 
407 void
408 BListView::KeyDown(const char* bytes, int32 numBytes)
409 {
410 	bool extend = fListType == B_MULTIPLE_SELECTION_LIST
411 		&& (modifiers() & B_SHIFT_KEY) != 0;
412 
413 	if (fFirstSelected == -1
414 		&& (bytes[0] == B_UP_ARROW || bytes[0] == B_DOWN_ARROW)) {
415 		// nothing is selected yet, select the first enabled item
416 		int32 lastItem = CountItems() - 1;
417 		for (int32 i = 0; i <= lastItem; i++) {
418 			if (ItemAt(i)->IsEnabled()) {
419 				Select(i);
420 				break;
421 			}
422 		}
423 		return;
424 	}
425 
426 	switch (bytes[0]) {
427 		case B_UP_ARROW:
428 		{
429 			if (fAnchorIndex > 0) {
430 				if (!extend || fAnchorIndex <= fFirstSelected) {
431 					for (int32 i = 1; fAnchorIndex - i >= 0; i++) {
432 						if (ItemAt(fAnchorIndex - i)->IsEnabled()) {
433 							// Select the previous enabled item
434 							Select(fAnchorIndex - i, extend);
435 							break;
436 						}
437 					}
438 				} else {
439 					Deselect(fAnchorIndex);
440 					do
441 						fAnchorIndex--;
442 					while (fAnchorIndex > 0
443 						&& !ItemAt(fAnchorIndex)->IsEnabled());
444 				}
445 			}
446 
447 			ScrollToSelection();
448 			break;
449 		}
450 
451 		case B_DOWN_ARROW:
452 		{
453 			int32 lastItem = CountItems() - 1;
454 			if (fAnchorIndex < lastItem) {
455 				if (!extend || fAnchorIndex >= fLastSelected) {
456 					for (int32 i = 1; fAnchorIndex + i <= lastItem; i++) {
457 						if (ItemAt(fAnchorIndex + i)->IsEnabled()) {
458 							// Select the next enabled item
459 							Select(fAnchorIndex + i, extend);
460 							break;
461 						}
462 					}
463 				} else {
464 					Deselect(fAnchorIndex);
465 					do
466 						fAnchorIndex++;
467 					while (fAnchorIndex < lastItem
468 						&& !ItemAt(fAnchorIndex)->IsEnabled());
469 				}
470 			}
471 
472 			ScrollToSelection();
473 			break;
474 		}
475 
476 		case B_HOME:
477 			if (extend) {
478 				Select(0, fAnchorIndex, true);
479 				fAnchorIndex = 0;
480 			} else {
481 				// select the first enabled item
482 				int32 lastItem = CountItems() - 1;
483 				for (int32 i = 0; i <= lastItem; i++) {
484 					if (ItemAt(i)->IsEnabled()) {
485 						Select(i, false);
486 						break;
487 					}
488 				}
489 			}
490 
491 			ScrollToSelection();
492 			break;
493 
494 		case B_END:
495 			if (extend) {
496 				Select(fAnchorIndex, CountItems() - 1, true);
497 				fAnchorIndex = CountItems() - 1;
498 			} else {
499 				// select the last enabled item
500 				for (int32 i = CountItems() - 1; i >= 0; i--) {
501 					if (ItemAt(i)->IsEnabled()) {
502 						Select(i, false);
503 						break;
504 					}
505 				}
506 			}
507 
508 			ScrollToSelection();
509 			break;
510 
511 		case B_PAGE_UP:
512 		{
513 			BPoint scrollOffset(LeftTop());
514 			scrollOffset.y = std::max(0.0f, scrollOffset.y - Bounds().Height());
515 			ScrollTo(scrollOffset);
516 			break;
517 		}
518 
519 		case B_PAGE_DOWN:
520 		{
521 			BPoint scrollOffset(LeftTop());
522 			if (BListItem* item = LastItem()) {
523 				scrollOffset.y += Bounds().Height();
524 				scrollOffset.y = std::min(item->Bottom() - Bounds().Height(),
525 					scrollOffset.y);
526 			}
527 			ScrollTo(scrollOffset);
528 			break;
529 		}
530 
531 		case B_RETURN:
532 		case B_SPACE:
533 			Invoke();
534 			break;
535 
536 		default:
537 			BView::KeyDown(bytes, numBytes);
538 	}
539 }
540 
541 
542 void
543 BListView::MouseDown(BPoint where)
544 {
545 	if (!IsFocus()) {
546 		MakeFocus();
547 		Sync();
548 		Window()->UpdateIfNeeded();
549 	}
550 
551 	int32 index = IndexOf(where);
552 	int32 modifiers = 0;
553 
554 	BMessage* message = Looper()->CurrentMessage();
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 				if (index >= fFirstSelected && index < fLastSelected)
600 					// clicked inside of selected items block, deselect all
601 					// but from the first selected item to the clicked item
602 					DeselectExcept(fFirstSelected, index);
603 				else
604 					Select(std::min(index, fFirstSelected), std::max(index,
605 						fLastSelected));
606 			} else {
607 				if ((modifiers & B_COMMAND_KEY) != 0) {
608 					// toggle selection state of clicked item (like in Tracker)
609 					if (ItemAt(index)->IsSelected())
610 						Deselect(index);
611 					else
612 						Select(index, true);
613 				} else if (!ItemAt(index)->IsSelected())
614 					// To enable multi-select drag and drop, we only
615 					// exclusively select a single item if it's not one of the
616 					// already selected items. This behavior gives the mouse
617 					// tracking thread the opportunity to initiate the
618 					// multi-selection drag with all the items still selected.
619 					Select(index);
620 			}
621 		} else {
622 			// toggle selection state of clicked item (except drag & drop)
623 			if ((modifiers & B_COMMAND_KEY) != 0 && ItemAt(index)->IsSelected())
624 				Deselect(index);
625 			else
626 				Select(index);
627 		}
628 	} else if ((modifiers & B_COMMAND_KEY) == 0)
629 		DeselectAll();
630 
631 	BView::MouseDown(where);
632 }
633 
634 
635 void
636 BListView::MouseUp(BPoint where)
637 {
638 	BView::MouseUp(where);
639 }
640 
641 
642 void
643 BListView::MouseMoved(BPoint where, uint32 code, const BMessage* dragMessage)
644 {
645 	BView::MouseMoved(where, code, dragMessage);
646 }
647 
648 
649 bool
650 BListView::InitiateDrag(BPoint where, int32 index, bool wasSelected)
651 {
652 	return false;
653 }
654 
655 
656 // #pragma mark -
657 
658 
659 void
660 BListView::ResizeToPreferred()
661 {
662 	BView::ResizeToPreferred();
663 }
664 
665 
666 void
667 BListView::GetPreferredSize(float *_width, float *_height)
668 {
669 	int32 count = CountItems();
670 
671 	if (count > 0) {
672 		float maxWidth = 0.0;
673 		for (int32 i = 0; i < count; i++) {
674 			float itemWidth = ItemAt(i)->Width();
675 			if (itemWidth > maxWidth)
676 				maxWidth = itemWidth;
677 		}
678 
679 		if (_width != NULL)
680 			*_width = maxWidth;
681 		if (_height != NULL)
682 			*_height = ItemAt(count - 1)->Bottom();
683 	} else
684 		BView::GetPreferredSize(_width, _height);
685 }
686 
687 
688 BSize
689 BListView::MinSize()
690 {
691 	// We need a stable min size: the BView implementation uses
692 	// GetPreferredSize(), which by default just returns the current size.
693 	return BLayoutUtils::ComposeSize(ExplicitMinSize(), BSize(10, 10));
694 }
695 
696 
697 BSize
698 BListView::MaxSize()
699 {
700 	return BView::MaxSize();
701 }
702 
703 
704 BSize
705 BListView::PreferredSize()
706 {
707 	// We need a stable preferred size: the BView implementation uses
708 	// GetPreferredSize(), which by default just returns the current size.
709 	return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), BSize(100, 50));
710 }
711 
712 
713 // #pragma mark -
714 
715 
716 void
717 BListView::MakeFocus(bool focused)
718 {
719 	if (IsFocus() == focused)
720 		return;
721 
722 	BView::MakeFocus(focused);
723 
724 	if (fScrollView)
725 		fScrollView->SetBorderHighlighted(focused);
726 }
727 
728 
729 void
730 BListView::SetFont(const BFont* font, uint32 mask)
731 {
732 	BView::SetFont(font, mask);
733 
734 	if (Window() != NULL && !Window()->InViewTransaction())
735 		_UpdateItems();
736 }
737 
738 
739 void
740 BListView::ScrollTo(BPoint point)
741 {
742 	BView::ScrollTo(point);
743 }
744 
745 
746 // #pragma mark - List ops
747 
748 
749 bool
750 BListView::AddItem(BListItem* item, int32 index)
751 {
752 	if (!fList.AddItem(item, index))
753 		return false;
754 
755 	if (fFirstSelected != -1 && index <= fFirstSelected)
756 		fFirstSelected++;
757 
758 	if (fLastSelected != -1 && index <= fLastSelected)
759 		fLastSelected++;
760 
761 	if (Window()) {
762 		BFont font;
763 		GetFont(&font);
764 		item->SetTop((index > 0) ? ItemAt(index - 1)->Bottom() + 1.0 : 0.0);
765 
766 		item->Update(this, &font);
767 		_RecalcItemTops(index + 1);
768 
769 		_FixupScrollBar();
770 		_InvalidateFrom(index);
771 	}
772 
773 	return true;
774 }
775 
776 
777 bool
778 BListView::AddItem(BListItem* item)
779 {
780 	if (!fList.AddItem(item))
781 		return false;
782 
783 	// No need to adapt selection, as this item is the last in the list
784 
785 	if (Window()) {
786 		BFont font;
787 		GetFont(&font);
788 		int32 index = CountItems() - 1;
789 		item->SetTop((index > 0) ? ItemAt(index - 1)->Bottom() + 1.0 : 0.0);
790 
791 		item->Update(this, &font);
792 
793 		_FixupScrollBar();
794 		InvalidateItem(CountItems() - 1);
795 	}
796 
797 	return true;
798 }
799 
800 
801 bool
802 BListView::AddList(BList* list, int32 index)
803 {
804 	if (!fList.AddList(list, index))
805 		return false;
806 
807 	int32 count = list->CountItems();
808 
809 	if (fFirstSelected != -1 && index < fFirstSelected)
810 		fFirstSelected += count;
811 
812 	if (fLastSelected != -1 && index < fLastSelected)
813 		fLastSelected += count;
814 
815 	if (Window()) {
816 		BFont font;
817 		GetFont(&font);
818 
819 		for (int32 i = index; i <= (index + count - 1); i++) {
820 			ItemAt(i)->SetTop((i > 0) ? ItemAt(i - 1)->Bottom() + 1.0 : 0.0);
821 			ItemAt(i)->Update(this, &font);
822 		}
823 
824 		_RecalcItemTops(index + count - 1);
825 
826 		_FixupScrollBar();
827 		Invalidate(); // TODO
828 	}
829 
830 	return true;
831 }
832 
833 
834 bool
835 BListView::AddList(BList* list)
836 {
837 	return AddList(list, CountItems());
838 }
839 
840 
841 BListItem*
842 BListView::RemoveItem(int32 index)
843 {
844 	BListItem* item = ItemAt(index);
845 	if (item == NULL)
846 		return NULL;
847 
848 	if (item->IsSelected())
849 		Deselect(index);
850 
851 	if (!fList.RemoveItem(item))
852 		return NULL;
853 
854 	if (fFirstSelected != -1 && index < fFirstSelected)
855 		fFirstSelected--;
856 
857 	if (fLastSelected != -1 && index < fLastSelected)
858 		fLastSelected--;
859 
860 	if (fAnchorIndex != -1 && index < fAnchorIndex)
861 		fAnchorIndex--;
862 
863 	_RecalcItemTops(index);
864 
865 	_InvalidateFrom(index);
866 	_FixupScrollBar();
867 
868 	return item;
869 }
870 
871 
872 bool
873 BListView::RemoveItem(BListItem* item)
874 {
875 	return BListView::RemoveItem(IndexOf(item)) != NULL;
876 }
877 
878 
879 bool
880 BListView::RemoveItems(int32 index, int32 count)
881 {
882 	if (index >= fList.CountItems())
883 		index = -1;
884 
885 	if (index < 0)
886 		return false;
887 
888 	if (fAnchorIndex != -1 && index < fAnchorIndex)
889 		fAnchorIndex = index;
890 
891 	fList.RemoveItems(index, count);
892 	if (index < fList.CountItems())
893 		_RecalcItemTops(index);
894 
895 	Invalidate();
896 	return true;
897 }
898 
899 
900 void
901 BListView::SetSelectionMessage(BMessage* message)
902 {
903 	delete fSelectMessage;
904 	fSelectMessage = message;
905 }
906 
907 
908 void
909 BListView::SetInvocationMessage(BMessage* message)
910 {
911 	BInvoker::SetMessage(message);
912 }
913 
914 
915 BMessage*
916 BListView::InvocationMessage() const
917 {
918 	return BInvoker::Message();
919 }
920 
921 
922 uint32
923 BListView::InvocationCommand() const
924 {
925 	return BInvoker::Command();
926 }
927 
928 
929 BMessage*
930 BListView::SelectionMessage() const
931 {
932 	return fSelectMessage;
933 }
934 
935 
936 uint32
937 BListView::SelectionCommand() const
938 {
939 	if (fSelectMessage)
940 		return fSelectMessage->what;
941 
942 	return 0;
943 }
944 
945 
946 void
947 BListView::SetListType(list_view_type type)
948 {
949 	if (fListType == B_MULTIPLE_SELECTION_LIST
950 		&& type == B_SINGLE_SELECTION_LIST) {
951 		Select(CurrentSelection(0));
952 	}
953 
954 	fListType = type;
955 }
956 
957 
958 list_view_type
959 BListView::ListType() const
960 {
961 	return fListType;
962 }
963 
964 
965 BListItem*
966 BListView::ItemAt(int32 index) const
967 {
968 	return (BListItem*)fList.ItemAt(index);
969 }
970 
971 
972 int32
973 BListView::IndexOf(BListItem* item) const
974 {
975 	if (Window()) {
976 		if (item != NULL) {
977 			int32 index = IndexOf(BPoint(0.0, item->Top()));
978 			if (index >= 0 && fList.ItemAt(index) == item)
979 				return index;
980 
981 			return -1;
982 		}
983 	}
984 	return fList.IndexOf(item);
985 }
986 
987 
988 int32
989 BListView::IndexOf(BPoint point) const
990 {
991 	int32 low = 0;
992 	int32 high = fList.CountItems() - 1;
993 	int32 mid = -1;
994 	float frameTop = -1.0;
995 	float frameBottom = 1.0;
996 
997 	// binary search the list
998 	while (high >= low) {
999 		mid = (low + high) / 2;
1000 		frameTop = ItemAt(mid)->Top();
1001 		frameBottom = ItemAt(mid)->Bottom();
1002 		if (point.y < frameTop)
1003 			high = mid - 1;
1004 		else if (point.y > frameBottom)
1005 			low = mid + 1;
1006 		else
1007 			return mid;
1008 	}
1009 
1010 	return -1;
1011 }
1012 
1013 
1014 BListItem*
1015 BListView::FirstItem() const
1016 {
1017 	return (BListItem*)fList.FirstItem();
1018 }
1019 
1020 
1021 BListItem*
1022 BListView::LastItem() const
1023 {
1024 	return (BListItem*)fList.LastItem();
1025 }
1026 
1027 
1028 bool
1029 BListView::HasItem(BListItem *item) const
1030 {
1031 	return IndexOf(item) != -1;
1032 }
1033 
1034 
1035 int32
1036 BListView::CountItems() const
1037 {
1038 	return fList.CountItems();
1039 }
1040 
1041 
1042 void
1043 BListView::MakeEmpty()
1044 {
1045 	if (fList.IsEmpty())
1046 		return;
1047 
1048 	_DeselectAll(-1, -1);
1049 	fList.MakeEmpty();
1050 
1051 	if (Window()) {
1052 		_FixupScrollBar();
1053 		Invalidate();
1054 	}
1055 }
1056 
1057 
1058 bool
1059 BListView::IsEmpty() const
1060 {
1061 	return fList.IsEmpty();
1062 }
1063 
1064 
1065 void
1066 BListView::DoForEach(bool (*func)(BListItem*))
1067 {
1068 	fList.DoForEach(reinterpret_cast<bool (*)(void*)>(func));
1069 }
1070 
1071 
1072 void
1073 BListView::DoForEach(bool (*func)(BListItem*, void*), void* arg)
1074 {
1075 	fList.DoForEach(reinterpret_cast<bool (*)(void*, void*)>(func), arg);
1076 }
1077 
1078 
1079 const BListItem**
1080 BListView::Items() const
1081 {
1082 	return (const BListItem**)fList.Items();
1083 }
1084 
1085 
1086 void
1087 BListView::InvalidateItem(int32 index)
1088 {
1089 	Invalidate(ItemFrame(index));
1090 }
1091 
1092 
1093 void
1094 BListView::ScrollToSelection()
1095 {
1096 	BRect itemFrame = ItemFrame(CurrentSelection(0));
1097 
1098 	if (Bounds().Contains(itemFrame))
1099 		return;
1100 
1101 	float scrollPos = itemFrame.top < Bounds().top ?
1102 		itemFrame.top : itemFrame.bottom - Bounds().Height();
1103 
1104 	if (itemFrame.top - scrollPos < Bounds().top)
1105 		scrollPos = itemFrame.top;
1106 
1107 	ScrollTo(itemFrame.left, scrollPos);
1108 }
1109 
1110 
1111 void
1112 BListView::Select(int32 index, bool extend)
1113 {
1114 	if (_Select(index, extend)) {
1115 		SelectionChanged();
1116 		InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED);
1117 	}
1118 }
1119 
1120 
1121 void
1122 BListView::Select(int32 start, int32 finish, bool extend)
1123 {
1124 	if (_Select(start, finish, extend)) {
1125 		SelectionChanged();
1126 		InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED);
1127 	}
1128 }
1129 
1130 
1131 bool
1132 BListView::IsItemSelected(int32 index) const
1133 {
1134 	BListItem* item = ItemAt(index);
1135 	if (item != NULL)
1136 		return item->IsSelected();
1137 
1138 	return false;
1139 }
1140 
1141 
1142 int32
1143 BListView::CurrentSelection(int32 index) const
1144 {
1145 	if (fFirstSelected == -1)
1146 		return -1;
1147 
1148 	if (index == 0)
1149 		return fFirstSelected;
1150 
1151 	for (int32 i = fFirstSelected; i <= fLastSelected; i++) {
1152 		if (ItemAt(i)->IsSelected()) {
1153 			if (index == 0)
1154 				return i;
1155 
1156 			index--;
1157 		}
1158 	}
1159 
1160 	return -1;
1161 }
1162 
1163 
1164 status_t
1165 BListView::Invoke(BMessage* message)
1166 {
1167 	// Note, this is more or less a copy of BControl::Invoke() and should
1168 	// stay that way (ie. changes done there should be adopted here)
1169 
1170 	bool notify = false;
1171 	uint32 kind = InvokeKind(&notify);
1172 
1173 	BMessage clone(kind);
1174 	status_t err = B_BAD_VALUE;
1175 
1176 	if (!message && !notify)
1177 		message = Message();
1178 
1179 	if (!message) {
1180 		if (!IsWatched())
1181 			return err;
1182 	} else
1183 		clone = *message;
1184 
1185 	clone.AddInt64("when", (int64)system_time());
1186 	clone.AddPointer("source", this);
1187 	clone.AddMessenger("be:sender", BMessenger(this));
1188 
1189 	if (fListType == B_SINGLE_SELECTION_LIST)
1190 		clone.AddInt32("index", fFirstSelected);
1191 	else {
1192 		if (fFirstSelected >= 0) {
1193 			for (int32 i = fFirstSelected; i <= fLastSelected; i++) {
1194 				if (ItemAt(i)->IsSelected())
1195 					clone.AddInt32("index", i);
1196 			}
1197 		}
1198 	}
1199 
1200 	if (message)
1201 		err = BInvoker::Invoke(&clone);
1202 
1203 	SendNotices(kind, &clone);
1204 
1205 	return err;
1206 }
1207 
1208 
1209 void
1210 BListView::DeselectAll()
1211 {
1212 	if (_DeselectAll(-1, -1)) {
1213 		SelectionChanged();
1214 		InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED);
1215 	}
1216 }
1217 
1218 
1219 void
1220 BListView::DeselectExcept(int32 exceptFrom, int32 exceptTo)
1221 {
1222 	if (exceptFrom > exceptTo || exceptFrom < 0 || exceptTo < 0)
1223 		return;
1224 
1225 	if (_DeselectAll(exceptFrom, exceptTo)) {
1226 		SelectionChanged();
1227 		InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED);
1228 	}
1229 }
1230 
1231 
1232 void
1233 BListView::Deselect(int32 index)
1234 {
1235 	if (_Deselect(index)) {
1236 		SelectionChanged();
1237 		InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED);
1238 	}
1239 }
1240 
1241 
1242 void
1243 BListView::SelectionChanged()
1244 {
1245 	// Hook method to be implemented by subclasses
1246 }
1247 
1248 
1249 void
1250 BListView::SortItems(int (*cmp)(const void *, const void *))
1251 {
1252 	if (_DeselectAll(-1, -1)) {
1253 		SelectionChanged();
1254 		InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED);
1255 	}
1256 
1257 	fList.SortItems(cmp);
1258 	_RecalcItemTops(0);
1259 	Invalidate();
1260 }
1261 
1262 
1263 bool
1264 BListView::SwapItems(int32 a, int32 b)
1265 {
1266 	MiscData data;
1267 
1268 	data.swap.a = a;
1269 	data.swap.b = b;
1270 
1271 	return DoMiscellaneous(B_SWAP_OP, &data);
1272 }
1273 
1274 
1275 bool
1276 BListView::MoveItem(int32 from, int32 to)
1277 {
1278 	MiscData data;
1279 
1280 	data.move.from = from;
1281 	data.move.to = to;
1282 
1283 	return DoMiscellaneous(B_MOVE_OP, &data);
1284 }
1285 
1286 
1287 bool
1288 BListView::ReplaceItem(int32 index, BListItem* item)
1289 {
1290 	MiscData data;
1291 
1292 	data.replace.index = index;
1293 	data.replace.item = item;
1294 
1295 	return DoMiscellaneous(B_REPLACE_OP, &data);
1296 }
1297 
1298 
1299 BRect
1300 BListView::ItemFrame(int32 index)
1301 {
1302 	BRect frame = Bounds();
1303 	if (index < 0 || index >= CountItems()) {
1304 		frame.top = 0;
1305 		frame.bottom = -1;
1306 	} else {
1307 		BListItem* item = ItemAt(index);
1308 		frame.top = item->Top();
1309 		frame.bottom = item->Bottom();
1310 	}
1311 	return frame;
1312 }
1313 
1314 
1315 // #pragma mark -
1316 
1317 
1318 BHandler*
1319 BListView::ResolveSpecifier(BMessage* message, int32 index,
1320 	BMessage* specifier, int32 what, const char* property)
1321 {
1322 	BPropertyInfo propInfo(sProperties);
1323 
1324 	if (propInfo.FindMatch(message, 0, specifier, what, property) < 0) {
1325 		return BView::ResolveSpecifier(message, index, specifier, what,
1326 			property);
1327 	}
1328 
1329 	// TODO: msg->AddInt32("_match_code_", );
1330 
1331 	return this;
1332 }
1333 
1334 
1335 status_t
1336 BListView::GetSupportedSuites(BMessage* data)
1337 {
1338 	if (data == NULL)
1339 		return B_BAD_VALUE;
1340 
1341 	status_t err = data->AddString("suites", "suite/vnd.Be-list-view");
1342 
1343 	BPropertyInfo propertyInfo(sProperties);
1344 	if (err == B_OK)
1345 		err = data->AddFlat("messages", &propertyInfo);
1346 
1347 	if (err == B_OK)
1348 		return BView::GetSupportedSuites(data);
1349 	return err;
1350 }
1351 
1352 
1353 status_t
1354 BListView::Perform(perform_code code, void* _data)
1355 {
1356 	switch (code) {
1357 		case PERFORM_CODE_MIN_SIZE:
1358 			((perform_data_min_size*)_data)->return_value
1359 				= BListView::MinSize();
1360 			return B_OK;
1361 		case PERFORM_CODE_MAX_SIZE:
1362 			((perform_data_max_size*)_data)->return_value
1363 				= BListView::MaxSize();
1364 			return B_OK;
1365 		case PERFORM_CODE_PREFERRED_SIZE:
1366 			((perform_data_preferred_size*)_data)->return_value
1367 				= BListView::PreferredSize();
1368 			return B_OK;
1369 		case PERFORM_CODE_LAYOUT_ALIGNMENT:
1370 			((perform_data_layout_alignment*)_data)->return_value
1371 				= BListView::LayoutAlignment();
1372 			return B_OK;
1373 		case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH:
1374 			((perform_data_has_height_for_width*)_data)->return_value
1375 				= BListView::HasHeightForWidth();
1376 			return B_OK;
1377 		case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH:
1378 		{
1379 			perform_data_get_height_for_width* data
1380 				= (perform_data_get_height_for_width*)_data;
1381 			BListView::GetHeightForWidth(data->width, &data->min, &data->max,
1382 				&data->preferred);
1383 			return B_OK;
1384 		}
1385 		case PERFORM_CODE_SET_LAYOUT:
1386 		{
1387 			perform_data_set_layout* data = (perform_data_set_layout*)_data;
1388 			BListView::SetLayout(data->layout);
1389 			return B_OK;
1390 		}
1391 		case PERFORM_CODE_LAYOUT_INVALIDATED:
1392 		{
1393 			perform_data_layout_invalidated* data
1394 				= (perform_data_layout_invalidated*)_data;
1395 			BListView::LayoutInvalidated(data->descendants);
1396 			return B_OK;
1397 		}
1398 		case PERFORM_CODE_DO_LAYOUT:
1399 		{
1400 			BListView::DoLayout();
1401 			return B_OK;
1402 		}
1403 	}
1404 
1405 	return BView::Perform(code, _data);
1406 }
1407 
1408 
1409 bool
1410 BListView::DoMiscellaneous(MiscCode code, MiscData* data)
1411 {
1412 	if (code > B_SWAP_OP)
1413 		return false;
1414 
1415 	switch (code) {
1416 		case B_NO_OP:
1417 			break;
1418 
1419 		case B_REPLACE_OP:
1420 			return _ReplaceItem(data->replace.index, data->replace.item);
1421 
1422 		case B_MOVE_OP:
1423 			return _MoveItem(data->move.from, data->move.to);
1424 
1425 		case B_SWAP_OP:
1426 			return _SwapItems(data->swap.a, data->swap.b);
1427 	}
1428 
1429 	return false;
1430 }
1431 
1432 
1433 // #pragma mark -
1434 
1435 
1436 void BListView::_ReservedListView2() {}
1437 void BListView::_ReservedListView3() {}
1438 void BListView::_ReservedListView4() {}
1439 
1440 
1441 BListView&
1442 BListView::operator=(const BListView& /*other*/)
1443 {
1444 	return *this;
1445 }
1446 
1447 
1448 // #pragma mark -
1449 
1450 
1451 void
1452 BListView::_InitObject(list_view_type type)
1453 {
1454 	fListType = type;
1455 	fFirstSelected = -1;
1456 	fLastSelected = -1;
1457 	fAnchorIndex = -1;
1458 	fSelectMessage = NULL;
1459 	fScrollView = NULL;
1460 
1461 	fTrack = new track_data;
1462 	fTrack->drag_start = B_ORIGIN;
1463 	fTrack->item_index = -1;
1464 	fTrack->was_selected = false;
1465 	fTrack->try_drag = false;
1466 	fTrack->is_dragging = false;
1467 	fTrack->last_click_time = 0;
1468 
1469 	SetViewUIColor(B_LIST_BACKGROUND_COLOR);
1470 	SetLowUIColor(B_LIST_BACKGROUND_COLOR);
1471 }
1472 
1473 
1474 void
1475 BListView::_FixupScrollBar()
1476 {
1477 
1478 	BScrollBar* vertScroller = ScrollBar(B_VERTICAL);
1479 	if (vertScroller != NULL) {
1480 		BRect bounds = Bounds();
1481 		int32 count = CountItems();
1482 
1483 		float itemHeight = 0.0;
1484 
1485 		if (CountItems() > 0)
1486 			itemHeight = ItemAt(CountItems() - 1)->Bottom();
1487 
1488 		if (bounds.Height() > itemHeight) {
1489 			// no scrolling
1490 			vertScroller->SetRange(0.0, 0.0);
1491 			vertScroller->SetValue(0.0);
1492 				// also scrolls ListView to the top
1493 		} else {
1494 			vertScroller->SetRange(0.0, itemHeight - bounds.Height() - 1.0);
1495 			vertScroller->SetProportion(bounds.Height () / itemHeight);
1496 			// scroll up if there is empty room on bottom
1497 			if (itemHeight < bounds.bottom)
1498 				ScrollBy(0.0, bounds.bottom - itemHeight);
1499 		}
1500 
1501 		if (count != 0)
1502 			vertScroller->SetSteps(
1503 				ceilf(FirstItem()->Height()), bounds.Height());
1504 	}
1505 
1506 	BScrollBar* horizontalScroller = ScrollBar(B_HORIZONTAL);
1507 	if (horizontalScroller != NULL) {
1508 		float w;
1509 		GetPreferredSize(&w, NULL);
1510 		BRect scrollBarSize = horizontalScroller->Bounds();
1511 
1512 		if (w <= scrollBarSize.Width()) {
1513 			// no scrolling
1514 			horizontalScroller->SetRange(0.0, 0.0);
1515 			horizontalScroller->SetValue(0.0);
1516 		} else {
1517 			horizontalScroller->SetRange(0, w - scrollBarSize.Width());
1518 			horizontalScroller->SetProportion(scrollBarSize.Width() / w);
1519 		}
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