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