xref: /haiku/src/kits/interface/ListView.cpp (revision 1b8f7f13a3dc70e0e903cb94248220b40b732204)
1 /*
2  * Copyright (c) 2001-2007, 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 	if (fList.IsEmpty())
741 		return;
742 
743 	_DeselectAll(-1, -1);
744 	fList.MakeEmpty();
745 
746 	if (Window()) {
747 		_FixupScrollBar();
748 		Invalidate();
749 	}
750 }
751 
752 // IsEmpty
753 bool
754 BListView::IsEmpty() const
755 {
756 	return fList.IsEmpty();
757 }
758 
759 // DoForEach
760 void
761 BListView::DoForEach(bool (*func)(BListItem*))
762 {
763 	fList.DoForEach(reinterpret_cast<bool (*)(void*)>(func));
764 }
765 
766 // DoForEach
767 void
768 BListView::DoForEach(bool (*func)(BListItem*, void*), void* arg )
769 {
770 	fList.DoForEach(reinterpret_cast<bool (*)(void*, void*)>(func), arg);
771 }
772 
773 // Items
774 const BListItem**
775 BListView::Items() const
776 {
777 	return (const BListItem**)fList.Items();
778 }
779 
780 // InvalidateItem
781 void
782 BListView::InvalidateItem(int32 index)
783 {
784 	Invalidate(ItemFrame(index));
785 }
786 
787 // ScrollToSelection
788 void
789 BListView::ScrollToSelection()
790 {
791 	BRect itemFrame = ItemFrame(CurrentSelection(0));
792 
793 	if (Bounds().Intersects(itemFrame.InsetByCopy(0.0f, 2.0f)))
794 		return;
795 
796 	if (itemFrame.top < Bounds().top)
797 		ScrollTo(0, itemFrame.top);
798 	else
799 		ScrollTo(0, itemFrame.bottom - Bounds().Height());
800 }
801 
802 
803 void
804 BListView::Select(int32 index, bool extend)
805 {
806 	if (_Select(index, extend)) {
807 		SelectionChanged();
808 		InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED);
809 	}
810 }
811 
812 
813 void
814 BListView::Select(int32 start, int32 finish, bool extend)
815 {
816 	if (_Select(start, finish, extend)) {
817 		SelectionChanged();
818 		InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED);
819 	}
820 }
821 
822 
823 bool
824 BListView::IsItemSelected(int32 index) const
825 {
826 	BListItem *item = ItemAt(index);
827 	if (item)
828 		return item->IsSelected();
829 
830 	return false;
831 }
832 
833 // CurrentSelection
834 int32
835 BListView::CurrentSelection(int32 index) const
836 {
837 	if (fFirstSelected == -1)
838 		return -1;
839 
840 	if (index == 0)
841 		return fFirstSelected;
842 
843 	for (int32 i = fFirstSelected; i <= fLastSelected; i++) {
844 		if (ItemAt(i)->IsSelected()) {
845 			if (index == 0)
846 				return i;
847 
848 			index--;
849 		}
850 	}
851 
852 	return -1;
853 }
854 
855 // Invoke
856 status_t
857 BListView::Invoke(BMessage *message)
858 {
859 	// Note, this is more or less a copy of BControl::Invoke() and should
860 	// stay that way (ie. changes done there should be adopted here)
861 
862 	bool notify = false;
863 	uint32 kind = InvokeKind(&notify);
864 
865 	BMessage clone(kind);
866 	status_t err = B_BAD_VALUE;
867 
868 	if (!message && !notify)
869 		message = Message();
870 
871 	if (!message) {
872 		if (!IsWatched())
873 			return err;
874 	} else
875 		clone = *message;
876 
877 	clone.AddInt64("when", (int64)system_time());
878 	clone.AddPointer("source", this);
879 	clone.AddMessenger("be:sender", BMessenger(this));
880 
881 	if (fListType == B_SINGLE_SELECTION_LIST)
882 		clone.AddInt32("index", fFirstSelected);
883 	else {
884 		if (fFirstSelected >= 0) {
885 			for (int32 i = fFirstSelected; i <= fLastSelected; i++) {
886 				if (ItemAt(i)->IsSelected())
887 					clone.AddInt32("index", i);
888 			}
889 		}
890 	}
891 
892 	if (message)
893 		err = BInvoker::Invoke(&clone);
894 
895 	SendNotices(kind, &clone);
896 
897 	return err;
898 }
899 
900 
901 void
902 BListView::DeselectAll()
903 {
904 	if (_DeselectAll(-1, -1)) {
905 		SelectionChanged();
906 		InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED);
907 	}
908 }
909 
910 
911 void
912 BListView::DeselectExcept(int32 exceptFrom, int32 exceptTo)
913 {
914 	if (exceptFrom > exceptTo || exceptFrom < 0 || exceptTo < 0)
915 		return;
916 
917 	if (_DeselectAll(exceptFrom, exceptTo)) {
918 		SelectionChanged();
919 		InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED);
920 	}
921 }
922 
923 
924 void
925 BListView::Deselect(int32 index)
926 {
927 	if (_Deselect(index)) {
928 		SelectionChanged();
929 		InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED);
930 	}
931 }
932 
933 
934 void
935 BListView::SelectionChanged()
936 {
937 	// Hook method to be implemented by subclasses
938 }
939 
940 
941 void
942 BListView::SortItems(int (*cmp)(const void *, const void *))
943 {
944 	if (_DeselectAll(-1, -1)) {
945 		SelectionChanged();
946 		InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED);
947 	}
948 
949 	fList.SortItems(cmp);
950 	Invalidate();
951 }
952 
953 // SwapItems
954 bool
955 BListView::SwapItems(int32 a, int32 b)
956 {
957 	MiscData data;
958 
959 	data.swap.a = a;
960 	data.swap.b = b;
961 
962 	return DoMiscellaneous(B_SWAP_OP, &data);
963 }
964 
965 // MoveItem
966 bool
967 BListView::MoveItem(int32 from, int32 to)
968 {
969 	MiscData data;
970 
971 	data.move.from = from;
972 	data.move.to = to;
973 
974 	return DoMiscellaneous(B_MOVE_OP, &data);
975 }
976 
977 // ReplaceItem
978 bool
979 BListView::ReplaceItem(int32 index, BListItem *item)
980 {
981 	MiscData data;
982 
983 	data.replace.index = index;
984 	data.replace.item = item;
985 
986 	return DoMiscellaneous(B_REPLACE_OP, &data);
987 }
988 
989 // AttachedToWindow
990 void
991 BListView::AttachedToWindow()
992 {
993 	BView::AttachedToWindow();
994 	_FontChanged();
995 
996 	if (!Messenger().IsValid())
997 		SetTarget(Window(), NULL);
998 
999 	_FixupScrollBar();
1000 }
1001 
1002 // FrameMoved
1003 void
1004 BListView::FrameMoved(BPoint new_position)
1005 {
1006 	BView::FrameMoved(new_position);
1007 }
1008 
1009 // ItemFrame
1010 BRect
1011 BListView::ItemFrame(int32 index)
1012 {
1013 	BRect frame(0, 0, Bounds().Width(), -1);
1014 
1015 	if (index < 0 || index >= CountItems())
1016 		return frame;
1017 
1018 	// TODO: this is very expensive, the (last) offsets could be cached
1019 	for (int32 i = 0; i <= index; i++) {
1020 		frame.top = frame.bottom + 1;
1021 		frame.bottom += (float)ceil(ItemAt(i)->Height());
1022 	}
1023 
1024 	return frame;
1025 }
1026 
1027 
1028 BHandler*
1029 BListView::ResolveSpecifier(BMessage* message, int32 index,
1030 	BMessage* specifier, int32 form, const char* property)
1031 {
1032 	BPropertyInfo propInfo(sProperties);
1033 
1034 	if (propInfo.FindMatch(message, 0, specifier, form, property) < 0)
1035 		return BView::ResolveSpecifier(message, index, specifier, form, property);
1036 
1037 	// TODO: msg->AddInt32("_match_code_", );
1038 
1039 	return this;
1040 }
1041 
1042 
1043 status_t
1044 BListView::GetSupportedSuites(BMessage* data)
1045 {
1046 	if (data == NULL)
1047 		return B_BAD_VALUE;
1048 
1049 	status_t err = data->AddString("suites", "suite/vnd.Be-list-view");
1050 
1051 	BPropertyInfo propertyInfo(sProperties);
1052 	if (err == B_OK)
1053 		err = data->AddFlat("messages", &propertyInfo);
1054 
1055 	if (err == B_OK)
1056 		return BView::GetSupportedSuites(data);
1057 	return err;
1058 }
1059 
1060 // Perform
1061 status_t
1062 BListView::Perform(perform_code d, void *arg)
1063 {
1064 	return BView::Perform(d, arg);
1065 }
1066 
1067 // WindowActivated
1068 void
1069 BListView::WindowActivated(bool state)
1070 {
1071 	BView::WindowActivated(state);
1072 }
1073 
1074 // DetachedFromWindow
1075 void
1076 BListView::DetachedFromWindow()
1077 {
1078 	BView::DetachedFromWindow();
1079 }
1080 
1081 // InitiateDrag
1082 bool
1083 BListView::InitiateDrag(BPoint point, int32 index, bool wasSelected)
1084 {
1085 	return false;
1086 }
1087 
1088 // ResizeToPreferred
1089 void
1090 BListView::ResizeToPreferred()
1091 {
1092 	BView::ResizeToPreferred();
1093 }
1094 
1095 // GetPreferredSize
1096 void
1097 BListView::GetPreferredSize(float *width, float *height)
1098 {
1099 	BView::GetPreferredSize(width, height);
1100 }
1101 
1102 // AllAttached
1103 void
1104 BListView::AllAttached()
1105 {
1106 	BView::AllAttached();
1107 }
1108 
1109 // AllDetached
1110 void
1111 BListView::AllDetached()
1112 {
1113 	BView::AllDetached();
1114 }
1115 
1116 BSize
1117 BListView::MinSize()
1118 {
1119 	// We need a stable min size: the BView implementation uses
1120 	// GetPreferredSize(), which by default just returns the current size.
1121 	return BLayoutUtils::ComposeSize(ExplicitMinSize(), BSize(10, 10));
1122 }
1123 
1124 
1125 BSize
1126 BListView::MaxSize()
1127 {
1128 	return BView::MaxSize();
1129 }
1130 
1131 
1132 BSize
1133 BListView::PreferredSize()
1134 {
1135 	// We need a stable preferred size: the BView implementation uses
1136 	// GetPreferredSize(), which by default just returns the current size.
1137 	return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), BSize(100, 50));
1138 }
1139 
1140 
1141 // DoMiscellaneous
1142 bool
1143 BListView::DoMiscellaneous(MiscCode code, MiscData *data)
1144 {
1145 	if (code > B_SWAP_OP)
1146 		return false;
1147 
1148 	switch (code) {
1149 		case B_NO_OP:
1150 			break;
1151 
1152 		case B_REPLACE_OP:
1153 			return _ReplaceItem(data->replace.index, data->replace.item);
1154 
1155 		case B_MOVE_OP:
1156 			return _MoveItem(data->move.from, data->move.to);
1157 
1158 		case B_SWAP_OP:
1159 			return _SwapItems(data->swap.a, data->swap.b);
1160 	}
1161 
1162 	return false;
1163 }
1164 
1165 void BListView::_ReservedListView2() {}
1166 void BListView::_ReservedListView3() {}
1167 void BListView::_ReservedListView4() {}
1168 
1169 BListView &BListView::operator=(const BListView &)
1170 {
1171 	return *this;
1172 }
1173 
1174 // _InitObject
1175 void
1176 BListView::_InitObject(list_view_type type)
1177 {
1178 	fListType = type;
1179 	fFirstSelected = -1;
1180 	fLastSelected = -1;
1181 	fAnchorIndex = -1;
1182 	fWidth = Bounds().Width();
1183 	fSelectMessage = NULL;
1184 	fScrollView = NULL;
1185 	fTrack = new track_data;
1186 	fTrack->try_drag = false;
1187 	fTrack->item_index = -1;
1188 }
1189 
1190 // _FixupScrollBar
1191 void
1192 BListView::_FixupScrollBar()
1193 {
1194 	BScrollBar* vertScroller = ScrollBar(B_VERTICAL);
1195 
1196 	if (!vertScroller)
1197 		return;
1198 
1199 	BRect bounds = Bounds();
1200 	int32 count = CountItems();
1201 
1202 	float itemHeight = 0;
1203 	for (int32 i = 0; BListItem* item = ItemAt(i); i++) {
1204 		itemHeight += item->Height();
1205 	}
1206 
1207 	if (bounds.Height() > itemHeight) {
1208 		// no scrolling
1209 		vertScroller->SetRange(0.0, 0.0);
1210 		vertScroller->SetValue(0.0);
1211 			// also scrolls ListView to the top
1212 	} else {
1213 		vertScroller->SetRange(0.0, itemHeight - bounds.Height() - 1.0);
1214 		vertScroller->SetProportion(bounds.Height () / itemHeight);
1215 		// scroll up if there is empty room on bottom
1216 		if (itemHeight < bounds.bottom) {
1217 			ScrollBy(0.0, bounds.bottom - itemHeight);
1218 		}
1219 	}
1220 
1221 	if (count != 0) {
1222 		vertScroller->SetSteps((float)ceil(FirstItem()->Height()),
1223 			bounds.Height());
1224 	}
1225 }
1226 
1227 // _InvalidateFrom
1228 void
1229 BListView::_InvalidateFrom(int32 index)
1230 {
1231 	// make sure index is behind last valid index
1232 	int32 count = CountItems();
1233 	if (index >= count) {
1234 		index = count;
1235 	}
1236 	// take the item before the wanted one,
1237 	// because that might already be removed
1238 	index--;
1239 	BRect dirty = Bounds();
1240 	if (index >= 0) {
1241 		dirty.top = ItemFrame(index).bottom + 1;
1242 	}
1243 	Invalidate(dirty);
1244 }
1245 
1246 // _FontChanged
1247 void
1248 BListView::_FontChanged()
1249 {
1250 	BFont font;
1251 	GetFont(&font);
1252 
1253 	for (int i = 0; i < CountItems (); i ++)
1254 		ItemAt(i)->Update(this, &font);
1255 }
1256 
1257 
1258 /*!
1259 	Selects the item at the specified \a index, and returns \c true in
1260 	case the selection was changed because of this method.
1261 	If \a extend is \c false, all previously selected items are deselected.
1262 */
1263 bool
1264 BListView::_Select(int32 index, bool extend)
1265 {
1266 	if (index < 0 || index >= CountItems())
1267 		return false;
1268 
1269 	// only lock the window when there is one
1270 	BAutolock locker(Window());
1271 	if (Window() && !locker.IsLocked())
1272 		return false;
1273 
1274 	bool changed = false;
1275 
1276 	if (fFirstSelected != -1 && !extend)
1277 		changed = _DeselectAll(index, index);
1278 
1279 	BListItem* item = ItemAt(index);
1280 	if (!item->IsEnabled() || item->IsSelected()) {
1281 		// if the item is already selected, or can't be selected,
1282 		// we're done here
1283 		return changed;
1284 	}
1285 
1286 	// keep track of first and last selected item
1287 	if (fFirstSelected == -1) {
1288 		// no previous selection
1289 		fFirstSelected = index;
1290 		fLastSelected = index;
1291 	} else if (index < fFirstSelected) {
1292 		fFirstSelected = index;
1293 	} else if (index > fLastSelected) {
1294 		fLastSelected = index;
1295 	}
1296 
1297 	ItemAt(index)->Select();
1298 	if (Window())
1299 		InvalidateItem(index);
1300 
1301 	return true;
1302 }
1303 
1304 
1305 /*!
1306 	Selects the items between \a from and \a to, and returns \c true in
1307 	case the selection was changed because of this method.
1308 	If \a extend is \c false, all previously selected items are deselected.
1309 */
1310 bool
1311 BListView::_Select(int32 from, int32 to, bool extend)
1312 {
1313 	if (to < from)
1314 		return false;
1315 
1316 	BAutolock locker(Window());
1317 	if (Window() && !locker.IsLocked())
1318 		return false;
1319 
1320 	bool changed = false;
1321 
1322 	if (fFirstSelected != -1 && !extend)
1323 		changed = _DeselectAll(from, to);
1324 
1325 	if (fFirstSelected == -1) {
1326 		fFirstSelected = from;
1327 		fLastSelected = to;
1328 	} else if (from < fFirstSelected)
1329 		fFirstSelected = from;
1330 	else if (to > fLastSelected)
1331 		fLastSelected = to;
1332 
1333 	for (int32 i = from; i <= to; ++i) {
1334 		BListItem *item = ItemAt(i);
1335 		if (item && !item->IsSelected()) {
1336 			item->Select();
1337 			if (Window())
1338 				InvalidateItem(i);
1339 			changed = true;
1340 		}
1341 	}
1342 
1343 	return changed;
1344 }
1345 
1346 
1347 bool
1348 BListView::_Deselect(int32 index)
1349 {
1350 	if (index < 0 || index >= CountItems())
1351 		return false;
1352 
1353 	BWindow *window = Window();
1354 	BAutolock locker(window);
1355 	if (window && !locker.IsLocked())
1356 		return false;
1357 
1358 	BListItem *item = ItemAt(index);
1359 
1360 	if (item && item->IsSelected()) {
1361 		BRect frame(ItemFrame(index));
1362 		BRect bounds(Bounds());
1363 
1364 		item->Deselect();
1365 
1366 		if (fFirstSelected == index && fLastSelected == index) {
1367 			fFirstSelected = -1;
1368 			fLastSelected = -1;
1369 		} else {
1370 			if (fFirstSelected == index)
1371 				fFirstSelected = _CalcFirstSelected(index);
1372 
1373 			if (fLastSelected == index)
1374 				fLastSelected = _CalcLastSelected(index);
1375 		}
1376 
1377 		if (window && bounds.Intersects(frame))
1378 			DrawItem(ItemAt(index), frame, true);
1379 	}
1380 
1381 	return true;
1382 }
1383 
1384 
1385 bool
1386 BListView::_DeselectAll(int32 exceptFrom, int32 exceptTo)
1387 {
1388 	if (fFirstSelected == -1)
1389 		return false;
1390 
1391 	BAutolock locker(Window());
1392 	if (Window() && !locker.IsLocked())
1393 		return false;
1394 
1395 	bool changed = false;
1396 
1397 	for (int32 index = fFirstSelected; index <= fLastSelected; index++) {
1398 		// don't deselect the items we shouldn't deselect
1399 		if (exceptFrom != -1 && exceptFrom <= index && exceptTo >= index)
1400 			continue;
1401 
1402 		BListItem *item = ItemAt(index);
1403 		if (item && item->IsSelected()) {
1404 			item->Deselect();
1405 			InvalidateItem(index);
1406 			changed = true;
1407 		}
1408 	}
1409 
1410 	if (!changed)
1411 		return false;
1412 
1413 	if (exceptFrom != -1) {
1414 		fFirstSelected = _CalcFirstSelected(exceptFrom);
1415 		fLastSelected = _CalcLastSelected(exceptTo);
1416 	} else
1417 		fFirstSelected = fLastSelected = -1;
1418 
1419 	return true;
1420 }
1421 
1422 // _TryInitiateDrag
1423 bool
1424 BListView::_TryInitiateDrag(BPoint where)
1425 {
1426 	if (!fTrack->try_drag | fTrack->item_index < 0)
1427 		return false;
1428 
1429 	BPoint offset = where - fTrack->drag_start;
1430 	float dragDistance = sqrtf(offset.x * offset.x + offset.y * offset.y);
1431 
1432 	if (dragDistance > 5.0) {
1433 		fTrack->try_drag = false;
1434 		return InitiateDrag(fTrack->drag_start, fTrack->item_index, fTrack->was_selected);;
1435 	}
1436 	return false;
1437 }
1438 
1439 // _CalcFirstSelected
1440 int32
1441 BListView::_CalcFirstSelected(int32 after)
1442 {
1443 	if (after >= CountItems())
1444 		return -1;
1445 
1446 	int32 count = CountItems();
1447 	for (int32 i = after; i < count; i++) {
1448 		if (ItemAt(i)->IsSelected())
1449 			return i;
1450 	}
1451 
1452 	return -1;
1453 }
1454 
1455 
1456 int32
1457 BListView::_CalcLastSelected(int32 before)
1458 {
1459 	if (before < 0)
1460 		return -1;
1461 
1462 	before = min_c(CountItems() - 1, before);
1463 
1464 	for (int32 i = before; i >= 0; i--) {
1465 		if (ItemAt(i)->IsSelected())
1466 			return i;
1467 	}
1468 
1469 	return -1;
1470 }
1471 
1472 
1473 void
1474 BListView::DrawItem(BListItem *item, BRect itemRect, bool complete)
1475 {
1476 	item->DrawItem(this, itemRect, complete);
1477 }
1478 
1479 
1480 bool
1481 BListView::_SwapItems(int32 a, int32 b)
1482 {
1483 	// remember frames of items before anyhing happens,
1484 	// the tricky situation is when the two items have
1485 	// a different height
1486 	BRect aFrame = ItemFrame(a);
1487 	BRect bFrame = ItemFrame(b);
1488 
1489 	if (!fList.SwapItems(a, b))
1490 		return false;
1491 
1492 	if (a == b) {
1493 		// nothing to do, but success nevertheless
1494 		return true;
1495 	}
1496 
1497 	// track anchor item
1498 	if (fAnchorIndex == a)
1499 		fAnchorIndex = b;
1500 	else if (fAnchorIndex == b)
1501 		fAnchorIndex = a;
1502 
1503 	// track selection
1504 	// NOTE: this is only important if the selection status
1505 	// of both items is not the same
1506 	if (ItemAt(a)->IsSelected() != ItemAt(b)->IsSelected()) {
1507 		int32 first = min_c(a, b);
1508 		int32 last = max_c(a, b);
1509 		if (first < fFirstSelected || last > fLastSelected) {
1510 			first = min_c(first, fFirstSelected);
1511 			last = max_c(last, fLastSelected);
1512 			_RescanSelection(first, last);
1513 		}
1514 		// though the actually selected items stayed the
1515 		// same, the selection has still changed
1516 		SelectionChanged();
1517 	}
1518 
1519 	// take care of invalidation
1520 	if (Window()) {
1521 		// NOTE: window looper is assumed to be locked!
1522 		if (aFrame.Height() != bFrame.Height()) {
1523 			// items in between shifted visually
1524 			Invalidate(aFrame | bFrame);
1525 		} else {
1526 			Invalidate(aFrame);
1527 			Invalidate(bFrame);
1528 		}
1529 	}
1530 
1531 	return true;
1532 }
1533 
1534 
1535 bool
1536 BListView::_MoveItem(int32 from, int32 to)
1537 {
1538 	// remember item frames before doing anything
1539 	BRect frameFrom = ItemFrame(from);
1540 	BRect frameTo = ItemFrame(to);
1541 
1542 	if (!fList.MoveItem(from, to))
1543 		return false;
1544 
1545 	// track anchor item
1546 	if (fAnchorIndex == from)
1547 		fAnchorIndex = to;
1548 
1549 	// track selection
1550 	if (ItemAt(to)->IsSelected()) {
1551 		_RescanSelection(from, to);
1552 		// though the actually selected items stayed the
1553 		// same, the selection has still changed
1554 		SelectionChanged();
1555 	}
1556 
1557 	// take care of invalidation
1558 	if (Window()) {
1559 		// NOTE: window looper is assumed to be locked!
1560 		Invalidate(frameFrom | frameTo);
1561 	}
1562 
1563 	return true;
1564 }
1565 
1566 
1567 bool
1568 BListView::_ReplaceItem(int32 index, BListItem *item)
1569 {
1570 	if (!item)
1571 		return false;
1572 
1573 	BListItem* old = ItemAt(index);
1574 	if (!old)
1575 		return false;
1576 
1577 	BRect frame = ItemFrame(index);
1578 
1579 	bool selectionChanged = old->IsSelected() != item->IsSelected();
1580 
1581 	// replace item
1582 	if (!fList.ReplaceItem(index, item))
1583 		return false;
1584 
1585 	// tack selection
1586 	if (selectionChanged) {
1587 		int32 start = min_c(fFirstSelected, index);
1588 		int32 end = max_c(fLastSelected, index);
1589 		_RescanSelection(start, end);
1590 		SelectionChanged();
1591 	}
1592 
1593 	bool itemHeightChanged = frame != ItemFrame(index);
1594 
1595 	// take care of invalidation
1596 	if (Window()) {
1597 		// NOTE: window looper is assumed to be locked!
1598 		if (itemHeightChanged)
1599 			_InvalidateFrom(index);
1600 		else
1601 			Invalidate(frame);
1602 	}
1603 
1604 	if (itemHeightChanged)
1605 		_FixupScrollBar();
1606 
1607 	return true;
1608 }
1609 
1610 
1611 void
1612 BListView::_RescanSelection(int32 from, int32 to)
1613 {
1614 	if (from > to) {
1615 		int32 tmp = from;
1616 		from = to;
1617 		to = tmp;
1618 	}
1619 
1620 	from = max_c(0, from);
1621 	to = min_c(to, CountItems() - 1);
1622 
1623 	if (fAnchorIndex != -1) {
1624 		if (fAnchorIndex == from)
1625 			fAnchorIndex = to;
1626 		else if (fAnchorIndex == to)
1627 			fAnchorIndex = from;
1628 	}
1629 
1630 	for (int32 i = from; i <= to; i++) {
1631 		if (ItemAt(i)->IsSelected()) {
1632 			fFirstSelected = i;
1633 			break;
1634 		}
1635 	}
1636 
1637 	if (fFirstSelected > from)
1638 		from = fFirstSelected;
1639 	for (int32 i = from; i <= to; i++) {
1640 		if (ItemAt(i)->IsSelected())
1641 			fLastSelected = i;
1642 	}
1643 }
1644 
1645 
1646