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