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