xref: /haiku/src/kits/interface/ListView.cpp (revision 6dcd0ccf238263a3e5eb2e2a44e2ed0da1617a42)
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 	BWindow *window = Window();
1327 	BAutolock locker(window);
1328 	if (window && !locker.IsLocked())
1329 		return false;
1330 
1331 	BListItem *item = ItemAt(index);
1332 
1333 	if (item && item->IsSelected()) {
1334 		BRect frame(ItemFrame(index));
1335 		BRect bounds(Bounds());
1336 
1337 		item->Deselect();
1338 
1339 		if (fFirstSelected == index && fLastSelected == index) {
1340 			fFirstSelected = -1;
1341 			fLastSelected = -1;
1342 		} else {
1343 			if (fFirstSelected == index)
1344 				fFirstSelected = _CalcFirstSelected(index);
1345 
1346 			if (fLastSelected == index)
1347 				fLastSelected = _CalcLastSelected(index);
1348 		}
1349 
1350 		if (window && bounds.Intersects(frame))
1351 			DrawItem(ItemAt(index), frame, true);
1352 	}
1353 
1354 	return true;
1355 }
1356 
1357 
1358 bool
1359 BListView::_DeselectAll(int32 exceptFrom, int32 exceptTo)
1360 {
1361 	if (fFirstSelected == -1)
1362 		return false;
1363 
1364 	BAutolock locker(Window());
1365 	if (Window() && !locker.IsLocked())
1366 		return false;
1367 
1368 	bool changed = false;
1369 
1370 	for (int32 index = fFirstSelected; index <= fLastSelected; index++) {
1371 		// don't deselect the items we shouldn't deselect
1372 		if (exceptFrom != -1 && exceptFrom <= index && exceptTo >= index)
1373 			continue;
1374 
1375 		BListItem *item = ItemAt(index);
1376 		if (item && item->IsSelected()) {
1377 			item->Deselect();
1378 			InvalidateItem(index);
1379 			changed = true;
1380 		}
1381 	}
1382 
1383 	if (!changed)
1384 		return false;
1385 
1386 	if (exceptFrom != -1) {
1387 		fFirstSelected = _CalcFirstSelected(exceptFrom);
1388 		fLastSelected = _CalcLastSelected(exceptTo);
1389 	} else
1390 		fFirstSelected = fLastSelected = -1;
1391 
1392 	return true;
1393 }
1394 
1395 // _TryInitiateDrag
1396 bool
1397 BListView::_TryInitiateDrag(BPoint where)
1398 {
1399 	if (!fTrack->try_drag | fTrack->item_index < 0)
1400 		return false;
1401 
1402 	BPoint offset = where - fTrack->drag_start;
1403 	float dragDistance = sqrtf(offset.x * offset.x + offset.y * offset.y);
1404 
1405 	if (dragDistance > 5.0) {
1406 		fTrack->try_drag = false;
1407 		return InitiateDrag(fTrack->drag_start, fTrack->item_index, fTrack->was_selected);;
1408 	}
1409 	return false;
1410 }
1411 
1412 // _CalcFirstSelected
1413 int32
1414 BListView::_CalcFirstSelected(int32 after)
1415 {
1416 	if (after >= CountItems())
1417 		return -1;
1418 
1419 	int32 count = CountItems();
1420 	for (int32 i = after; i < count; i++) {
1421 		if (ItemAt(i)->IsSelected())
1422 			return i;
1423 	}
1424 
1425 	return -1;
1426 }
1427 
1428 
1429 int32
1430 BListView::_CalcLastSelected(int32 before)
1431 {
1432 	if (before < 0)
1433 		return -1;
1434 
1435 	before = min_c(CountItems() - 1, before);
1436 
1437 	for (int32 i = before; i >= 0; i--) {
1438 		if (ItemAt(i)->IsSelected())
1439 			return i;
1440 	}
1441 
1442 	return -1;
1443 }
1444 
1445 
1446 void
1447 BListView::DrawItem(BListItem *item, BRect itemRect, bool complete)
1448 {
1449 	item->DrawItem(this, itemRect, complete);
1450 }
1451 
1452 
1453 bool
1454 BListView::_SwapItems(int32 a, int32 b)
1455 {
1456 	// remember frames of items before anyhing happens,
1457 	// the tricky situation is when the two items have
1458 	// a different height
1459 	BRect aFrame = ItemFrame(a);
1460 	BRect bFrame = ItemFrame(b);
1461 
1462 	if (!fList.SwapItems(a, b))
1463 		return false;
1464 
1465 	if (a == b) {
1466 		// nothing to do, but success nevertheless
1467 		return true;
1468 	}
1469 
1470 	// track anchor item
1471 	if (fAnchorIndex == a)
1472 		fAnchorIndex = b;
1473 	else if (fAnchorIndex == b)
1474 		fAnchorIndex = a;
1475 
1476 	// track selection
1477 	// NOTE: this is only important if the selection status
1478 	// of both items is not the same
1479 	if (ItemAt(a)->IsSelected() != ItemAt(b)->IsSelected()) {
1480 		int32 first = min_c(a, b);
1481 		int32 last = max_c(a, b);
1482 		if (first < fFirstSelected || last > fLastSelected) {
1483 			first = min_c(first, fFirstSelected);
1484 			last = max_c(last, fLastSelected);
1485 			_RescanSelection(first, last);
1486 		}
1487 		// though the actually selected items stayed the
1488 		// same, the selection has still changed
1489 		SelectionChanged();
1490 	}
1491 
1492 	// take care of invalidation
1493 	if (Window()) {
1494 		// NOTE: window looper is assumed to be locked!
1495 		if (aFrame.Height() != bFrame.Height()) {
1496 			// items in between shifted visually
1497 			Invalidate(aFrame | bFrame);
1498 		} else {
1499 			Invalidate(aFrame);
1500 			Invalidate(bFrame);
1501 		}
1502 	}
1503 
1504 	return true;
1505 }
1506 
1507 
1508 bool
1509 BListView::_MoveItem(int32 from, int32 to)
1510 {
1511 	// remember item frames before doing anything
1512 	BRect frameFrom = ItemFrame(from);
1513 	BRect frameTo = ItemFrame(to);
1514 
1515 	if (!fList.MoveItem(from, to))
1516 		return false;
1517 
1518 	// track anchor item
1519 	if (fAnchorIndex == from)
1520 		fAnchorIndex = to;
1521 
1522 	// track selection
1523 	if (ItemAt(to)->IsSelected()) {
1524 		_RescanSelection(from, to);
1525 		// though the actually selected items stayed the
1526 		// same, the selection has still changed
1527 		SelectionChanged();
1528 	}
1529 
1530 	// take care of invalidation
1531 	if (Window()) {
1532 		// NOTE: window looper is assumed to be locked!
1533 		Invalidate(frameFrom | frameTo);
1534 	}
1535 
1536 	return true;
1537 }
1538 
1539 
1540 bool
1541 BListView::_ReplaceItem(int32 index, BListItem *item)
1542 {
1543 	if (!item)
1544 		return false;
1545 
1546 	BListItem* old = ItemAt(index);
1547 	if (!old)
1548 		return false;
1549 
1550 	BRect frame = ItemFrame(index);
1551 
1552 	bool selectionChanged = old->IsSelected() != item->IsSelected();
1553 
1554 	// replace item
1555 	if (!fList.ReplaceItem(index, item))
1556 		return false;
1557 
1558 	// tack selection
1559 	if (selectionChanged) {
1560 		int32 start = min_c(fFirstSelected, index);
1561 		int32 end = max_c(fLastSelected, index);
1562 		_RescanSelection(start, end);
1563 		SelectionChanged();
1564 	}
1565 
1566 	bool itemHeightChanged = frame != ItemFrame(index);
1567 
1568 	// take care of invalidation
1569 	if (Window()) {
1570 		// NOTE: window looper is assumed to be locked!
1571 		if (itemHeightChanged)
1572 			_InvalidateFrom(index);
1573 		else
1574 			Invalidate(frame);
1575 	}
1576 
1577 	if (itemHeightChanged)
1578 		_FixupScrollBar();
1579 
1580 	return true;
1581 }
1582 
1583 
1584 void
1585 BListView::_RescanSelection(int32 from, int32 to)
1586 {
1587 	if (from > to) {
1588 		int32 tmp = from;
1589 		from = to;
1590 		to = tmp;
1591 	}
1592 
1593 	from = max_c(0, from);
1594 	to = min_c(to, CountItems() - 1);
1595 
1596 	if (fAnchorIndex != -1) {
1597 		if (fAnchorIndex == from)
1598 			fAnchorIndex = to;
1599 		else if (fAnchorIndex == to)
1600 			fAnchorIndex = from;
1601 	}
1602 
1603 	for (int32 i = from; i <= to; i++) {
1604 		if (ItemAt(i)->IsSelected()) {
1605 			fFirstSelected = i;
1606 			break;
1607 		}
1608 	}
1609 
1610 	if (fFirstSelected > from)
1611 		from = fFirstSelected;
1612 	for (int32 i = from; i <= to; i++) {
1613 		if (ItemAt(i)->IsSelected())
1614 			fLastSelected = i;
1615 	}
1616 }
1617 
1618 
1619