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