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