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