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